Repository: apache/jackrabbit Branch: trunk Commit: e868deec6c10 Files: 3373 Total size: 33.8 MB Directory structure: gitextract_13_ffsv_/ ├── .asf.yaml ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── .mvn/ │ └── README.md ├── LICENSE.txt ├── NOTICE.txt ├── README.txt ├── RELEASE-NOTES.txt ├── assembly.xml ├── examples/ │ └── jackrabbit-firsthops/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── firsthops/ │ │ ├── FirstHop.java │ │ ├── SecondHop.java │ │ └── ThirdHop.java │ └── resources/ │ ├── log4j.properties │ └── test.xml ├── jackrabbit-aws-ext/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── aws/ │ │ └── ext/ │ │ ├── S3Constants.java │ │ ├── S3RequestDecorator.java │ │ ├── Utils.java │ │ └── ds/ │ │ ├── S3Backend.java │ │ ├── S3BackendResourceAbortableInputStream.java │ │ ├── S3DataStore.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── aws/ │ │ └── ext/ │ │ ├── TestAll.java │ │ └── ds/ │ │ ├── S3TestDataStore.java │ │ ├── TestS3DSAsyncTouch.java │ │ ├── TestS3DSWithSSES3.java │ │ ├── TestS3DSWithSmallCache.java │ │ ├── TestS3Ds.java │ │ └── TestS3DsCacheOff.java │ └── resources/ │ ├── aws.properties │ ├── log4j.properties │ └── repository_sample.xml ├── jackrabbit-core/ │ ├── HEADER.txt │ ├── README.txt │ ├── checkstyle-suppressions.xml │ ├── checkstyle.xml │ ├── jdepend.properties │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ └── NOTICE │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── core/ │ │ │ ├── AbstractNodeData.java │ │ │ ├── AddMixinOperation.java │ │ │ ├── BatchedItemOperations.java │ │ │ ├── CachingHierarchyManager.java │ │ │ ├── DefaultSecurityManager.java │ │ │ ├── HierarchyManager.java │ │ │ ├── HierarchyManagerImpl.java │ │ │ ├── ItemData.java │ │ │ ├── ItemImpl.java │ │ │ ├── ItemLifeCycleListener.java │ │ │ ├── ItemManager.java │ │ │ ├── ItemRefreshOperation.java │ │ │ ├── ItemRemoveOperation.java │ │ │ ├── ItemSaveOperation.java │ │ │ ├── ItemValidator.java │ │ │ ├── JackrabbitRepositoryStub.java │ │ │ ├── JackrabbitThreadPool.java │ │ │ ├── LazyItemIterator.java │ │ │ ├── LowPriorityTask.java │ │ │ ├── NamespaceRegistryImpl.java │ │ │ ├── NodeData.java │ │ │ ├── NodeDataRef.java │ │ │ ├── NodeImpl.java │ │ │ ├── NodeTypeInstanceHandler.java │ │ │ ├── PropertyData.java │ │ │ ├── PropertyImpl.java │ │ │ ├── ProtectedItemModifier.java │ │ │ ├── RemoveMixinOperation.java │ │ │ ├── RepositoryChecker.java │ │ │ ├── RepositoryContext.java │ │ │ ├── RepositoryCopier.java │ │ │ ├── RepositoryFactoryImpl.java │ │ │ ├── RepositoryImpl.java │ │ │ ├── RepositoryManagerImpl.java │ │ │ ├── SearchManager.java │ │ │ ├── SessionFactory.java │ │ │ ├── SessionImpl.java │ │ │ ├── SessionListener.java │ │ │ ├── SessionMoveOperation.java │ │ │ ├── SystemSession.java │ │ │ ├── TestContentLoader.java │ │ │ ├── TransientRepository.java │ │ │ ├── UserPerWorkspaceSecurityManager.java │ │ │ ├── VersionManagerImpl.java │ │ │ ├── WorkspaceImpl.java │ │ │ ├── WorkspaceManager.java │ │ │ ├── XASessionImpl.java │ │ │ ├── ZombieHierarchyManager.java │ │ │ ├── cache/ │ │ │ │ ├── AbstractCache.java │ │ │ │ ├── Cache.java │ │ │ │ ├── CacheAccessListener.java │ │ │ │ ├── CacheManager.java │ │ │ │ ├── ConcurrentCache.java │ │ │ │ └── GrowingLRUMap.java │ │ │ ├── cluster/ │ │ │ │ ├── ChangeLogRecord.java │ │ │ │ ├── ClusterContext.java │ │ │ │ ├── ClusterException.java │ │ │ │ ├── ClusterNode.java │ │ │ │ ├── ClusterOperation.java │ │ │ │ ├── ClusterRecord.java │ │ │ │ ├── ClusterRecordDeserializer.java │ │ │ │ ├── ClusterRecordProcessor.java │ │ │ │ ├── ClusterSession.java │ │ │ │ ├── DefaultClusterOperation.java │ │ │ │ ├── LockEventChannel.java │ │ │ │ ├── LockEventListener.java │ │ │ │ ├── LockRecord.java │ │ │ │ ├── NamespaceEventChannel.java │ │ │ │ ├── NamespaceEventListener.java │ │ │ │ ├── NamespaceRecord.java │ │ │ │ ├── NodeTypeEventChannel.java │ │ │ │ ├── NodeTypeEventListener.java │ │ │ │ ├── NodeTypeRecord.java │ │ │ │ ├── PrivilegeEventChannel.java │ │ │ │ ├── PrivilegeEventListener.java │ │ │ │ ├── PrivilegeRecord.java │ │ │ │ ├── Update.java │ │ │ │ ├── UpdateEventChannel.java │ │ │ │ ├── UpdateEventListener.java │ │ │ │ ├── WorkspaceEventChannel.java │ │ │ │ ├── WorkspaceListener.java │ │ │ │ └── WorkspaceRecord.java │ │ │ ├── config/ │ │ │ │ ├── AccessManagerConfig.java │ │ │ │ ├── BeanConfig.java │ │ │ │ ├── BeanConfigVisitor.java │ │ │ │ ├── BeanFactory.java │ │ │ │ ├── ClusterConfig.java │ │ │ │ ├── ConfigurationEntityResolver.java │ │ │ │ ├── ConfigurationErrorHandler.java │ │ │ │ ├── ConfigurationParser.java │ │ │ │ ├── ImportConfig.java │ │ │ │ ├── LoginModuleConfig.java │ │ │ │ ├── NoOpConfigVisitor.java │ │ │ │ ├── PersistenceManagerConfig.java │ │ │ │ ├── RepositoryConfig.java │ │ │ │ ├── RepositoryConfigurationParser.java │ │ │ │ ├── SearchConfig.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ ├── SecurityManagerConfig.java │ │ │ │ ├── SimpleBeanFactory.java │ │ │ │ ├── UserManagerConfig.java │ │ │ │ ├── VersioningConfig.java │ │ │ │ ├── WorkspaceConfig.java │ │ │ │ └── WorkspaceSecurityConfig.java │ │ │ ├── fs/ │ │ │ │ ├── db/ │ │ │ │ │ ├── DB2FileSystem.java │ │ │ │ │ ├── DatabaseFileSystem.java │ │ │ │ │ ├── DbFileSystem.java │ │ │ │ │ ├── DerbyFileSystem.java │ │ │ │ │ ├── JNDIDatabaseFileSystem.java │ │ │ │ │ ├── MSSqlFileSystem.java │ │ │ │ │ ├── Oracle9FileSystem.java │ │ │ │ │ └── OracleFileSystem.java │ │ │ │ └── mem/ │ │ │ │ ├── MemoryFile.java │ │ │ │ ├── MemoryFileSystem.java │ │ │ │ ├── MemoryFileSystemEntry.java │ │ │ │ └── MemoryFolder.java │ │ │ ├── gc/ │ │ │ │ └── GarbageCollector.java │ │ │ ├── id/ │ │ │ │ ├── ItemId.java │ │ │ │ ├── NodeId.java │ │ │ │ ├── NodeIdFactory.java │ │ │ │ ├── PropertyId.java │ │ │ │ └── SeededSecureRandom.java │ │ │ ├── jdkcompat/ │ │ │ │ └── Java23Subject.java │ │ │ ├── jndi/ │ │ │ │ ├── BindableRepository.java │ │ │ │ ├── BindableRepositoryFactory.java │ │ │ │ ├── RegistryHelper.java │ │ │ │ └── provider/ │ │ │ │ ├── DummyContext.java │ │ │ │ └── DummyInitialContextFactory.java │ │ │ ├── journal/ │ │ │ │ ├── AbstractJournal.java │ │ │ │ ├── AbstractRecord.java │ │ │ │ ├── AppendRecord.java │ │ │ │ ├── DatabaseJournal.java │ │ │ │ ├── DatabaseRecordIterator.java │ │ │ │ ├── DefaultRecordProducer.java │ │ │ │ ├── FileJournal.java │ │ │ │ ├── FileRecordIterator.java │ │ │ │ ├── FileRecordLog.java │ │ │ │ ├── FileRevision.java │ │ │ │ ├── InstanceRevision.java │ │ │ │ ├── JNDIDatabaseJournal.java │ │ │ │ ├── Journal.java │ │ │ │ ├── JournalException.java │ │ │ │ ├── JournalFactory.java │ │ │ │ ├── LockableFileRevision.java │ │ │ │ ├── MSSqlDatabaseJournal.java │ │ │ │ ├── MemoryJournal.java │ │ │ │ ├── MemoryRevision.java │ │ │ │ ├── OracleDatabaseJournal.java │ │ │ │ ├── ReadRecord.java │ │ │ │ ├── Record.java │ │ │ │ ├── RecordConsumer.java │ │ │ │ ├── RecordIterator.java │ │ │ │ ├── RecordProducer.java │ │ │ │ └── RotatingLogFile.java │ │ │ ├── lock/ │ │ │ │ ├── LockImpl.java │ │ │ │ ├── LockInfo.java │ │ │ │ ├── LockManager.java │ │ │ │ ├── LockManagerImpl.java │ │ │ │ ├── SessionLockManager.java │ │ │ │ ├── XAEnvironment.java │ │ │ │ ├── XALockImpl.java │ │ │ │ └── XALockManager.java │ │ │ ├── nodetype/ │ │ │ │ ├── BitSetENTCacheImpl.java │ │ │ │ ├── EffectiveNodeType.java │ │ │ │ ├── EffectiveNodeTypeCache.java │ │ │ │ ├── EffectiveNodeTypeCacheImpl.java │ │ │ │ ├── InvalidNodeTypeDefException.java │ │ │ │ ├── NodeTypeConflictException.java │ │ │ │ ├── NodeTypeDefStore.java │ │ │ │ ├── NodeTypeDefinitionImpl.java │ │ │ │ ├── NodeTypeImpl.java │ │ │ │ ├── NodeTypeManagerImpl.java │ │ │ │ ├── NodeTypeRegistry.java │ │ │ │ ├── NodeTypeRegistryListener.java │ │ │ │ ├── virtual/ │ │ │ │ │ ├── VirtualNodeTypeStateManager.java │ │ │ │ │ └── VirtualNodeTypeStateProvider.java │ │ │ │ └── xml/ │ │ │ │ ├── AdditionalNamespaceResolver.java │ │ │ │ ├── Constants.java │ │ │ │ ├── NodeTypeReader.java │ │ │ │ └── NodeTypeWriter.java │ │ │ ├── observation/ │ │ │ │ ├── ChangeLogBasedHierarchyMgr.java │ │ │ │ ├── DelegatingObservationDispatcher.java │ │ │ │ ├── DispatchAction.java │ │ │ │ ├── EventConsumer.java │ │ │ │ ├── EventDispatcher.java │ │ │ │ ├── EventFilter.java │ │ │ │ ├── EventImpl.java │ │ │ │ ├── EventJournalImpl.java │ │ │ │ ├── EventListenerIteratorImpl.java │ │ │ │ ├── EventState.java │ │ │ │ ├── EventStateCollection.java │ │ │ │ ├── EventStateCollectionFactory.java │ │ │ │ ├── FilteredEventIterator.java │ │ │ │ ├── ObservationDispatcher.java │ │ │ │ ├── ObservationManagerImpl.java │ │ │ │ └── SynchronousEventListener.java │ │ │ ├── persistence/ │ │ │ │ ├── AbstractPersistenceManager.java │ │ │ │ ├── CachingPersistenceManager.java │ │ │ │ ├── IterablePersistenceManager.java │ │ │ │ ├── PMContext.java │ │ │ │ ├── PersistenceCopier.java │ │ │ │ ├── PersistenceManager.java │ │ │ │ ├── bundle/ │ │ │ │ │ ├── AbstractBundlePersistenceManager.java │ │ │ │ │ ├── BundleFsPersistenceManager.java │ │ │ │ │ ├── ConsistencyCheckerError.java │ │ │ │ │ └── ConsistencyCheckerImpl.java │ │ │ │ ├── check/ │ │ │ │ │ ├── ConsistencyCheckListener.java │ │ │ │ │ ├── ConsistencyChecker.java │ │ │ │ │ ├── ConsistencyReport.java │ │ │ │ │ ├── ConsistencyReportImpl.java │ │ │ │ │ ├── ReportItem.java │ │ │ │ │ └── ReportItemImpl.java │ │ │ │ ├── db/ │ │ │ │ │ ├── DatabasePersistenceManager.java │ │ │ │ │ ├── DerbyPersistenceManager.java │ │ │ │ │ ├── JNDIDatabasePersistenceManager.java │ │ │ │ │ ├── MSSqlPersistenceManager.java │ │ │ │ │ ├── OraclePersistenceManager.java │ │ │ │ │ └── SimpleDbPersistenceManager.java │ │ │ │ ├── mem/ │ │ │ │ │ ├── InMemBundlePersistenceManager.java │ │ │ │ │ └── InMemPersistenceManager.java │ │ │ │ ├── obj/ │ │ │ │ │ └── ObjectPersistenceManager.java │ │ │ │ ├── pool/ │ │ │ │ │ ├── BundleDbPersistenceManager.java │ │ │ │ │ ├── DbNameIndex.java │ │ │ │ │ ├── DerbyPersistenceManager.java │ │ │ │ │ ├── H2PersistenceManager.java │ │ │ │ │ ├── MSSqlPersistenceManager.java │ │ │ │ │ ├── MySqlPersistenceManager.java │ │ │ │ │ ├── NGKDbNameIndex.java │ │ │ │ │ ├── Oracle9PersistenceManager.java │ │ │ │ │ ├── OraclePersistenceManager.java │ │ │ │ │ ├── PostgreSQLNameIndex.java │ │ │ │ │ └── PostgreSQLPersistenceManager.java │ │ │ │ ├── util/ │ │ │ │ │ ├── BLOBStore.java │ │ │ │ │ ├── BundleBinding.java │ │ │ │ │ ├── BundleDumper.java │ │ │ │ │ ├── BundleNames.java │ │ │ │ │ ├── BundleReader.java │ │ │ │ │ ├── BundleWriter.java │ │ │ │ │ ├── ErrorHandling.java │ │ │ │ │ ├── FileBasedIndex.java │ │ │ │ │ ├── FileSystemBLOBStore.java │ │ │ │ │ ├── HashMapIndex.java │ │ │ │ │ ├── NodeInfo.java │ │ │ │ │ ├── NodePropBundle.java │ │ │ │ │ ├── ResourceBasedBLOBStore.java │ │ │ │ │ └── Serializer.java │ │ │ │ └── xml/ │ │ │ │ └── XMLPersistenceManager.java │ │ │ ├── query/ │ │ │ │ ├── AQTQueryFactory.java │ │ │ │ ├── AbstractQueryHandler.java │ │ │ │ ├── AbstractQueryImpl.java │ │ │ │ ├── CompoundQueryFactory.java │ │ │ │ ├── ExecutableQuery.java │ │ │ │ ├── OnWorkspaceInconsistency.java │ │ │ │ ├── PropertyTypeRegistry.java │ │ │ │ ├── QOMQueryFactory.java │ │ │ │ ├── QueryFactory.java │ │ │ │ ├── QueryHandler.java │ │ │ │ ├── QueryHandlerContext.java │ │ │ │ ├── QueryHandlerFactory.java │ │ │ │ ├── QueryImpl.java │ │ │ │ ├── QueryManagerImpl.java │ │ │ │ ├── QueryObjectModelImpl.java │ │ │ │ └── lucene/ │ │ │ │ ├── AbstractExcerpt.java │ │ │ │ ├── AbstractIndex.java │ │ │ │ ├── AbstractNamespaceMappings.java │ │ │ │ ├── AbstractQueryHits.java │ │ │ │ ├── AbstractQueryImpl.java │ │ │ │ ├── AbstractWeight.java │ │ │ │ ├── AggregateRule.java │ │ │ │ ├── AggregateRuleImpl.java │ │ │ │ ├── CachingIndexReader.java │ │ │ │ ├── CachingMultiIndexReader.java │ │ │ │ ├── CaseTermQuery.java │ │ │ │ ├── ChildAxisQuery.java │ │ │ │ ├── ChildNodesQueryHits.java │ │ │ │ ├── CloseableHits.java │ │ │ │ ├── CommittableIndexReader.java │ │ │ │ ├── ConsistencyCheck.java │ │ │ │ ├── ConsistencyCheckError.java │ │ │ │ ├── DateField.java │ │ │ │ ├── DecimalField.java │ │ │ │ ├── DefaultHTMLExcerpt.java │ │ │ │ ├── DefaultHighlighter.java │ │ │ │ ├── DefaultQueryHits.java │ │ │ │ ├── DefaultRedoLog.java │ │ │ │ ├── DefaultRedoLogFactory.java │ │ │ │ ├── DefaultXMLExcerpt.java │ │ │ │ ├── DerefQuery.java │ │ │ │ ├── DescendantSelfAxisQuery.java │ │ │ │ ├── DocId.java │ │ │ │ ├── DocNumberCache.java │ │ │ │ ├── DocOrderScoreNodeIterator.java │ │ │ │ ├── DoubleField.java │ │ │ │ ├── DynamicPooledExecutor.java │ │ │ │ ├── EmptyTermDocs.java │ │ │ │ ├── ExcerptProvider.java │ │ │ │ ├── FieldComparatorBase.java │ │ │ │ ├── FieldComparatorDecorator.java │ │ │ │ ├── FieldNames.java │ │ │ │ ├── FieldSelectors.java │ │ │ │ ├── FileBasedNamespaceMappings.java │ │ │ │ ├── FilterMultiColumnQuery.java │ │ │ │ ├── FilterMultiColumnQueryHits.java │ │ │ │ ├── FilterSearcher.java │ │ │ │ ├── ForeignSegmentDocId.java │ │ │ │ ├── HierarchyResolver.java │ │ │ │ ├── HighlightingExcerptProvider.java │ │ │ │ ├── IDField.java │ │ │ │ ├── IOCounters.java │ │ │ │ ├── IndexDeletionPolicyImpl.java │ │ │ │ ├── IndexFormatVersion.java │ │ │ │ ├── IndexHistory.java │ │ │ │ ├── IndexInfo.java │ │ │ │ ├── IndexInfos.java │ │ │ │ ├── IndexListener.java │ │ │ │ ├── IndexMerger.java │ │ │ │ ├── IndexMigration.java │ │ │ │ ├── IndexingConfiguration.java │ │ │ │ ├── IndexingConfigurationEntityResolver.java │ │ │ │ ├── IndexingConfigurationImpl.java │ │ │ │ ├── IndexingQueue.java │ │ │ │ ├── IndexingQueueStore.java │ │ │ │ ├── JackrabbitAnalyzer.java │ │ │ │ ├── JackrabbitIndexReader.java │ │ │ │ ├── JackrabbitIndexSearcher.java │ │ │ │ ├── JackrabbitQuery.java │ │ │ │ ├── JackrabbitQueryParser.java │ │ │ │ ├── JackrabbitTermQuery.java │ │ │ │ ├── JoinQuery.java │ │ │ │ ├── LazyTextExtractorField.java │ │ │ │ ├── LengthSortComparator.java │ │ │ │ ├── LocalNameQuery.java │ │ │ │ ├── LocalNameRangeQuery.java │ │ │ │ ├── LongField.java │ │ │ │ ├── LowerCaseSortComparator.java │ │ │ │ ├── LuceneQueryBuilder.java │ │ │ │ ├── LuceneQueryFactory.java │ │ │ │ ├── LuceneQueryHits.java │ │ │ │ ├── MatchAllDocsQuery.java │ │ │ │ ├── MatchAllQuery.java │ │ │ │ ├── MatchAllScorer.java │ │ │ │ ├── MatchAllWeight.java │ │ │ │ ├── MoreLikeThis.java │ │ │ │ ├── MultiColumnQuery.java │ │ │ │ ├── MultiColumnQueryAdapter.java │ │ │ │ ├── MultiColumnQueryHits.java │ │ │ │ ├── MultiColumnQueryResult.java │ │ │ │ ├── MultiIndex.java │ │ │ │ ├── MultiIndexReader.java │ │ │ │ ├── MultiScorer.java │ │ │ │ ├── NSRegistryBasedNamespaceMappings.java │ │ │ │ ├── NamePathResolverImpl.java │ │ │ │ ├── NameQuery.java │ │ │ │ ├── NameRangeQuery.java │ │ │ │ ├── NamespaceMappings.java │ │ │ │ ├── NodeIndexer.java │ │ │ │ ├── NodeIteratorImpl.java │ │ │ │ ├── NodeTraversingQueryHits.java │ │ │ │ ├── NormalizeSortComparator.java │ │ │ │ ├── NotQuery.java │ │ │ │ ├── OffsetCharSequence.java │ │ │ │ ├── Ordering.java │ │ │ │ ├── ParentAxisQuery.java │ │ │ │ ├── PerQueryCache.java │ │ │ │ ├── PersistentIndex.java │ │ │ │ ├── PredicateDerefQuery.java │ │ │ │ ├── PropertiesSynonymProvider.java │ │ │ │ ├── PropertyMetaData.java │ │ │ │ ├── QueryHits.java │ │ │ │ ├── QueryHitsAdapter.java │ │ │ │ ├── QueryHitsQuery.java │ │ │ │ ├── QueryImpl.java │ │ │ │ ├── QueryResultImpl.java │ │ │ │ ├── RangeQuery.java │ │ │ │ ├── RangeScan.java │ │ │ │ ├── ReadOnlyIndexReader.java │ │ │ │ ├── Recovery.java │ │ │ │ ├── RedoLog.java │ │ │ │ ├── RedoLogFactory.java │ │ │ │ ├── RefCountingIndexReader.java │ │ │ │ ├── ReleaseableIndexReader.java │ │ │ │ ├── RowIteratorImpl.java │ │ │ │ ├── ScoreNode.java │ │ │ │ ├── ScoreNodeIterator.java │ │ │ │ ├── ScoreNodeIteratorImpl.java │ │ │ │ ├── SearchIndex.java │ │ │ │ ├── SharedFieldCache.java │ │ │ │ ├── SharedFieldComparatorSource.java │ │ │ │ ├── SharedIndexReader.java │ │ │ │ ├── SimilarityQuery.java │ │ │ │ ├── SimpleExcerptProvider.java │ │ │ │ ├── SingleColumnQueryResult.java │ │ │ │ ├── SingleTermDocs.java │ │ │ │ ├── SingletonTokenStream.java │ │ │ │ ├── SortedLuceneQueryHits.java │ │ │ │ ├── SortedMultiColumnQueryHits.java │ │ │ │ ├── SpellChecker.java │ │ │ │ ├── SpellSuggestion.java │ │ │ │ ├── SynonymProvider.java │ │ │ │ ├── TermDocsCache.java │ │ │ │ ├── TermFactory.java │ │ │ │ ├── TransformConstants.java │ │ │ │ ├── Transformable.java │ │ │ │ ├── UpperCaseSortComparator.java │ │ │ │ ├── Util.java │ │ │ │ ├── VolatileIndex.java │ │ │ │ ├── WeightedHTMLExcerpt.java │ │ │ │ ├── WeightedHighlighter.java │ │ │ │ ├── WeightedXMLExcerpt.java │ │ │ │ ├── WildcardNameQuery.java │ │ │ │ ├── WildcardQuery.java │ │ │ │ ├── WildcardTermEnum.java │ │ │ │ ├── constraint/ │ │ │ │ │ ├── AndConstraint.java │ │ │ │ │ ├── ChildNodeConstraint.java │ │ │ │ │ ├── ComparisonConstraint.java │ │ │ │ │ ├── Constraint.java │ │ │ │ │ ├── ConstraintBuilder.java │ │ │ │ │ ├── DescendantNodeConstraint.java │ │ │ │ │ ├── DynamicOperand.java │ │ │ │ │ ├── EvaluationContext.java │ │ │ │ │ ├── FullTextConstraint.java │ │ │ │ │ ├── FullTextSearchScoreOperand.java │ │ │ │ │ ├── HierarchyConstraint.java │ │ │ │ │ ├── LengthOperand.java │ │ │ │ │ ├── LikeConstraint.java │ │ │ │ │ ├── LowerCaseOperand.java │ │ │ │ │ ├── NodeLocalNameOperand.java │ │ │ │ │ ├── NodeNameOperand.java │ │ │ │ │ ├── NotConstraint.java │ │ │ │ │ ├── OrConstraint.java │ │ │ │ │ ├── PropertyExistenceConstraint.java │ │ │ │ │ ├── PropertyValueOperand.java │ │ │ │ │ ├── QueryConstraint.java │ │ │ │ │ ├── SameNodeConstraint.java │ │ │ │ │ ├── SelectorBasedConstraint.java │ │ │ │ │ └── UpperCaseOperand.java │ │ │ │ ├── directory/ │ │ │ │ │ ├── DirectoryManager.java │ │ │ │ │ ├── FSDirectoryManager.java │ │ │ │ │ ├── IndexInputStream.java │ │ │ │ │ ├── IndexOutputStream.java │ │ │ │ │ └── RAMDirectoryManager.java │ │ │ │ ├── hits/ │ │ │ │ │ ├── AbstractHitCollector.java │ │ │ │ │ ├── AdaptingHits.java │ │ │ │ │ ├── ArrayHits.java │ │ │ │ │ ├── BitSetHits.java │ │ │ │ │ ├── Hits.java │ │ │ │ │ ├── HitsIntersection.java │ │ │ │ │ └── ScorerHits.java │ │ │ │ ├── join/ │ │ │ │ │ ├── AbstractCondition.java │ │ │ │ │ ├── AbstractRow.java │ │ │ │ │ ├── AncestorNodeJoin.java │ │ │ │ │ ├── AncestorPathNodeJoin.java │ │ │ │ │ ├── ChildNodeJoin.java │ │ │ │ │ ├── ChildNodeJoinMerger.java │ │ │ │ │ ├── Condition.java │ │ │ │ │ ├── ConstraintSplitInfo.java │ │ │ │ │ ├── ConstraintSplitter.java │ │ │ │ │ ├── Constraints.java │ │ │ │ │ ├── DescendantNodeJoin.java │ │ │ │ │ ├── DescendantNodeJoinMerger.java │ │ │ │ │ ├── DescendantPathNodeJoin.java │ │ │ │ │ ├── EquiJoin.java │ │ │ │ │ ├── EquiJoinMerger.java │ │ │ │ │ ├── Join.java │ │ │ │ │ ├── JoinMerger.java │ │ │ │ │ ├── JoinRow.java │ │ │ │ │ ├── ParentNodeJoin.java │ │ │ │ │ ├── QueryEngine.java │ │ │ │ │ ├── RowPathComparator.java │ │ │ │ │ ├── SameNodeJoin.java │ │ │ │ │ ├── SameNodeJoinMerger.java │ │ │ │ │ ├── ScoreNodeMap.java │ │ │ │ │ ├── SelectorRow.java │ │ │ │ │ ├── SimpleQueryResult.java │ │ │ │ │ └── ValueComparator.java │ │ │ │ └── sort/ │ │ │ │ ├── AbstractFieldComparator.java │ │ │ │ ├── DynamicOperandFieldComparator.java │ │ │ │ ├── DynamicOperandFieldComparatorSource.java │ │ │ │ ├── RowComparator.java │ │ │ │ └── ValueComparableWrapper.java │ │ │ ├── retention/ │ │ │ │ ├── HoldImpl.java │ │ │ │ ├── RetentionManagerImpl.java │ │ │ │ ├── RetentionPolicyImpl.java │ │ │ │ ├── RetentionRegistry.java │ │ │ │ └── RetentionRegistryImpl.java │ │ │ ├── security/ │ │ │ │ ├── AMContext.java │ │ │ │ ├── AbstractAccessControlManager.java │ │ │ │ ├── AccessManager.java │ │ │ │ ├── AnonymousPrincipal.java │ │ │ │ ├── DefaultAccessManager.java │ │ │ │ ├── JackrabbitSecurityManager.java │ │ │ │ ├── SecurityConstants.java │ │ │ │ ├── SystemPrincipal.java │ │ │ │ ├── UserPrincipal.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── AbstractLoginModule.java │ │ │ │ │ ├── AuthContext.java │ │ │ │ │ ├── AuthContextProvider.java │ │ │ │ │ ├── Authentication.java │ │ │ │ │ ├── CallbackHandlerImpl.java │ │ │ │ │ ├── CredentialsCallback.java │ │ │ │ │ ├── CryptedSimpleCredentials.java │ │ │ │ │ ├── DefaultLoginModule.java │ │ │ │ │ ├── ImpersonationCallback.java │ │ │ │ │ ├── JAASAuthContext.java │ │ │ │ │ ├── LocalAuthContext.java │ │ │ │ │ ├── RepositoryCallback.java │ │ │ │ │ ├── SimpleCredentialsAuthentication.java │ │ │ │ │ └── token/ │ │ │ │ │ ├── CompatTokenProvider.java │ │ │ │ │ ├── TokenBasedAuthentication.java │ │ │ │ │ ├── TokenInfo.java │ │ │ │ │ └── TokenProvider.java │ │ │ │ ├── authorization/ │ │ │ │ │ ├── AbstractACLTemplate.java │ │ │ │ │ ├── AbstractAccessControlProvider.java │ │ │ │ │ ├── AbstractCompiledPermissions.java │ │ │ │ │ ├── AccessControlConstants.java │ │ │ │ │ ├── AccessControlEditor.java │ │ │ │ │ ├── AccessControlEntryImpl.java │ │ │ │ │ ├── AccessControlListener.java │ │ │ │ │ ├── AccessControlModifications.java │ │ │ │ │ ├── AccessControlObserver.java │ │ │ │ │ ├── AccessControlProvider.java │ │ │ │ │ ├── AccessControlProviderFactory.java │ │ │ │ │ ├── AccessControlProviderFactoryImpl.java │ │ │ │ │ ├── AccessControlUtils.java │ │ │ │ │ ├── CompiledPermissions.java │ │ │ │ │ ├── GlobPattern.java │ │ │ │ │ ├── NamedAccessControlPolicyImpl.java │ │ │ │ │ ├── Permission.java │ │ │ │ │ ├── PrivilegeBits.java │ │ │ │ │ ├── PrivilegeManagerImpl.java │ │ │ │ │ ├── PrivilegeRegistry.java │ │ │ │ │ ├── UnmodifiableAccessControlList.java │ │ │ │ │ ├── WorkspaceAccessManager.java │ │ │ │ │ ├── acl/ │ │ │ │ │ │ ├── ACLEditor.java │ │ │ │ │ │ ├── ACLProvider.java │ │ │ │ │ │ ├── ACLTemplate.java │ │ │ │ │ │ ├── CachingEntryCollector.java │ │ │ │ │ │ ├── CompiledPermissionsImpl.java │ │ │ │ │ │ ├── Entry.java │ │ │ │ │ │ ├── EntryCollector.java │ │ │ │ │ │ ├── EntryFilter.java │ │ │ │ │ │ └── EntryFilterImpl.java │ │ │ │ │ ├── combined/ │ │ │ │ │ │ ├── CombinedEditor.java │ │ │ │ │ │ └── CombinedProvider.java │ │ │ │ │ └── principalbased/ │ │ │ │ │ ├── ACLEditor.java │ │ │ │ │ ├── ACLProvider.java │ │ │ │ │ ├── ACLTemplate.java │ │ │ │ │ └── EntriesCache.java │ │ │ │ ├── principal/ │ │ │ │ │ ├── AbstractPrincipalIterator.java │ │ │ │ │ ├── AbstractPrincipalProvider.java │ │ │ │ │ ├── AdminPrincipal.java │ │ │ │ │ ├── DefaultPrincipalProvider.java │ │ │ │ │ ├── EveryonePrincipal.java │ │ │ │ │ ├── FallbackPrincipalProvider.java │ │ │ │ │ ├── GroupPrincipals.java │ │ │ │ │ ├── PrincipalImpl.java │ │ │ │ │ ├── PrincipalIteratorAdapter.java │ │ │ │ │ ├── PrincipalManagerImpl.java │ │ │ │ │ ├── PrincipalProvider.java │ │ │ │ │ ├── PrincipalProviderRegistry.java │ │ │ │ │ ├── ProviderRegistryImpl.java │ │ │ │ │ └── UnknownPrincipal.java │ │ │ │ ├── simple/ │ │ │ │ │ ├── SimpleAccessManager.java │ │ │ │ │ ├── SimpleLoginModule.java │ │ │ │ │ ├── SimpleSecurityManager.java │ │ │ │ │ └── SimpleWorkspaceAccessManager.java │ │ │ │ └── user/ │ │ │ │ ├── AuthorizableImpl.java │ │ │ │ ├── GroupImpl.java │ │ │ │ ├── ImpersonationImpl.java │ │ │ │ ├── IndexNodeResolver.java │ │ │ │ ├── MembershipCache.java │ │ │ │ ├── NodeResolver.java │ │ │ │ ├── PasswordUtility.java │ │ │ │ ├── TraversingNodeResolver.java │ │ │ │ ├── UserAccessControlProvider.java │ │ │ │ ├── UserConstants.java │ │ │ │ ├── UserImpl.java │ │ │ │ ├── UserImporter.java │ │ │ │ ├── UserManagerConfig.java │ │ │ │ ├── UserManagerImpl.java │ │ │ │ ├── UserPerWorkspaceUserManager.java │ │ │ │ ├── XPathQueryBuilder.java │ │ │ │ ├── XPathQueryEvaluator.java │ │ │ │ └── action/ │ │ │ │ ├── AbstractAuthorizableAction.java │ │ │ │ ├── AccessControlAction.java │ │ │ │ ├── AuthorizableAction.java │ │ │ │ ├── ClearMembershipAction.java │ │ │ │ └── PasswordValidationAction.java │ │ │ ├── session/ │ │ │ │ ├── AddNodeOperation.java │ │ │ │ ├── NodeNameNormalizer.java │ │ │ │ ├── SessionContext.java │ │ │ │ ├── SessionItemOperation.java │ │ │ │ ├── SessionOperation.java │ │ │ │ ├── SessionRefreshOperation.java │ │ │ │ ├── SessionSaveOperation.java │ │ │ │ ├── SessionState.java │ │ │ │ └── SessionWriteOperation.java │ │ │ ├── state/ │ │ │ │ ├── ChangeLog.java │ │ │ │ ├── ChildNodeEntries.java │ │ │ │ ├── ChildNodeEntry.java │ │ │ │ ├── DefaultISMLocking.java │ │ │ │ ├── DummyUpdateEventChannel.java │ │ │ │ ├── FineGrainedISMLocking.java │ │ │ │ ├── ISMLocking.java │ │ │ │ ├── ISMLockingFactory.java │ │ │ │ ├── ItemState.java │ │ │ │ ├── ItemStateCache.java │ │ │ │ ├── ItemStateCacheFactory.java │ │ │ │ ├── ItemStateException.java │ │ │ │ ├── ItemStateListener.java │ │ │ │ ├── ItemStateManager.java │ │ │ │ ├── ItemStateReferenceCache.java │ │ │ │ ├── LocalItemStateManager.java │ │ │ │ ├── MLRUItemStateCache.java │ │ │ │ ├── ManagedMLRUItemStateCacheFactory.java │ │ │ │ ├── NameSet.java │ │ │ │ ├── NoSuchItemStateException.java │ │ │ │ ├── NodeReferences.java │ │ │ │ ├── NodeState.java │ │ │ │ ├── NodeStateListener.java │ │ │ │ ├── NodeStateMerger.java │ │ │ │ ├── PropertyState.java │ │ │ │ ├── SessionItemStateManager.java │ │ │ │ ├── SharedItemStateManager.java │ │ │ │ ├── StaleItemStateException.java │ │ │ │ ├── StateChangeDispatcher.java │ │ │ │ ├── UpdatableItemStateManager.java │ │ │ │ └── XAItemStateManager.java │ │ │ ├── stats/ │ │ │ │ └── StatManager.java │ │ │ ├── util/ │ │ │ │ ├── CooperativeFileLock.java │ │ │ │ ├── DOMBuilder.java │ │ │ │ ├── DOMWalker.java │ │ │ │ ├── EmptyLinkedMap.java │ │ │ │ ├── ReferenceChangeTracker.java │ │ │ │ ├── RepositoryLock.java │ │ │ │ ├── RepositoryLockMechanism.java │ │ │ │ ├── RepositoryLockMechanismFactory.java │ │ │ │ ├── StringIndex.java │ │ │ │ ├── XAReentrantLock.java │ │ │ │ └── XAReentrantWriterPreferenceReadWriteLock.java │ │ │ ├── value/ │ │ │ │ ├── BLOBFileValue.java │ │ │ │ ├── BLOBInDataStore.java │ │ │ │ ├── BLOBInMemory.java │ │ │ │ ├── BLOBInResource.java │ │ │ │ ├── BLOBInTempFile.java │ │ │ │ ├── BinaryValueImpl.java │ │ │ │ ├── InternalValue.java │ │ │ │ ├── InternalValueFactory.java │ │ │ │ ├── RefCountingBLOBFileValue.java │ │ │ │ └── ValueFactoryImpl.java │ │ │ ├── version/ │ │ │ │ ├── DateVersionSelector.java │ │ │ │ ├── InconsistentVersioningState.java │ │ │ │ ├── InternalActivity.java │ │ │ │ ├── InternalActivityImpl.java │ │ │ │ ├── InternalBaseline.java │ │ │ │ ├── InternalBaselineImpl.java │ │ │ │ ├── InternalFreeze.java │ │ │ │ ├── InternalFreezeImpl.java │ │ │ │ ├── InternalFrozenNode.java │ │ │ │ ├── InternalFrozenNodeImpl.java │ │ │ │ ├── InternalFrozenVHImpl.java │ │ │ │ ├── InternalFrozenVersionHistory.java │ │ │ │ ├── InternalVersion.java │ │ │ │ ├── InternalVersionHistory.java │ │ │ │ ├── InternalVersionHistoryImpl.java │ │ │ │ ├── InternalVersionImpl.java │ │ │ │ ├── InternalVersionItem.java │ │ │ │ ├── InternalVersionItemImpl.java │ │ │ │ ├── InternalVersionManager.java │ │ │ │ ├── InternalVersionManagerBase.java │ │ │ │ ├── InternalVersionManagerImpl.java │ │ │ │ ├── InternalXAVersionManager.java │ │ │ │ ├── LabelVersionSelector.java │ │ │ │ ├── NodeStateEx.java │ │ │ │ ├── VersionHistoryImpl.java │ │ │ │ ├── VersionHistoryInfo.java │ │ │ │ ├── VersionImpl.java │ │ │ │ ├── VersionItemStateManager.java │ │ │ │ ├── VersionItemStateProvider.java │ │ │ │ ├── VersionIteratorImpl.java │ │ │ │ ├── VersionManagerImplBase.java │ │ │ │ ├── VersionManagerImplConfig.java │ │ │ │ ├── VersionManagerImplMerge.java │ │ │ │ ├── VersionManagerImplRestore.java │ │ │ │ ├── VersionSelector.java │ │ │ │ ├── VersionSet.java │ │ │ │ └── VersioningLock.java │ │ │ ├── virtual/ │ │ │ │ ├── AbstractVISProvider.java │ │ │ │ ├── VirtualItemStateProvider.java │ │ │ │ ├── VirtualNodeState.java │ │ │ │ ├── VirtualPropertyState.java │ │ │ │ └── VirtualValueProvider.java │ │ │ └── xml/ │ │ │ ├── AccessControlImporter.java │ │ │ ├── BufferedStringValue.java │ │ │ ├── ClonedInputSource.java │ │ │ ├── DefaultProtectedItemImporter.java │ │ │ ├── DefaultProtectedNodeImporter.java │ │ │ ├── DefaultProtectedPropertyImporter.java │ │ │ ├── DocViewImportHandler.java │ │ │ ├── ImportHandler.java │ │ │ ├── Importer.java │ │ │ ├── NamespaceContext.java │ │ │ ├── NodeInfo.java │ │ │ ├── PropInfo.java │ │ │ ├── ProtectedItemImporter.java │ │ │ ├── ProtectedNodeImporter.java │ │ │ ├── ProtectedPropertyImporter.java │ │ │ ├── SessionImporter.java │ │ │ ├── StringValue.java │ │ │ ├── SysViewImportHandler.java │ │ │ ├── TargetImportHandler.java │ │ │ ├── TextValue.java │ │ │ └── WorkspaceImporter.java │ │ ├── javadoc/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── core/ │ │ │ ├── config/ │ │ │ │ ├── doc-files/ │ │ │ │ │ └── class.uxf │ │ │ │ └── package.html │ │ │ ├── doc-files/ │ │ │ │ ├── RepositoryImpl.uxf │ │ │ │ ├── SessionImpl.uxf │ │ │ │ ├── WorkspaceImpl.uxf │ │ │ │ ├── WorkspaceInfo.uxf │ │ │ │ └── core.uxf │ │ │ ├── fs/ │ │ │ │ └── db/ │ │ │ │ └── package.html │ │ │ ├── observation/ │ │ │ │ └── doc-files/ │ │ │ │ └── class.uxf │ │ │ ├── package.html │ │ │ ├── persistence/ │ │ │ │ └── db/ │ │ │ │ └── package.html │ │ │ └── version/ │ │ │ └── package.html │ │ ├── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── javax.jcr.RepositoryFactory │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── core/ │ │ │ ├── JackrabbitRepositoryStub.properties │ │ │ ├── config/ │ │ │ │ ├── deprecated-classes.properties │ │ │ │ ├── repository-1.0.dtd │ │ │ │ ├── repository-1.2.dtd │ │ │ │ ├── repository-1.4.dtd │ │ │ │ ├── repository-1.5.dtd │ │ │ │ ├── repository-1.6.dtd │ │ │ │ ├── repository-2.0-elements.dtd │ │ │ │ ├── repository-2.0.dtd │ │ │ │ ├── repository-2.4-elements.dtd │ │ │ │ ├── repository-2.4.dtd │ │ │ │ ├── repository-2.6-elements.dtd │ │ │ │ └── repository-2.6.dtd │ │ │ ├── fs/ │ │ │ │ └── db/ │ │ │ │ ├── azure.ddl │ │ │ │ ├── daffodil.ddl │ │ │ │ ├── db2.ddl │ │ │ │ ├── default.ddl │ │ │ │ ├── derby.ddl │ │ │ │ ├── ingres.ddl │ │ │ │ ├── maxdb.ddl │ │ │ │ ├── mssql.ddl │ │ │ │ ├── mysql.ddl │ │ │ │ ├── oracle.ddl │ │ │ │ └── postgresql.ddl │ │ │ ├── journal/ │ │ │ │ ├── azure.ddl │ │ │ │ ├── db2.ddl │ │ │ │ ├── default.ddl │ │ │ │ ├── derby.ddl │ │ │ │ ├── h2.ddl │ │ │ │ ├── ingres.ddl │ │ │ │ ├── maxdb.ddl │ │ │ │ ├── mssql.ddl │ │ │ │ ├── mysql.ddl │ │ │ │ ├── oracle.ddl │ │ │ │ └── postgresql.ddl │ │ │ ├── nodetype/ │ │ │ │ └── builtin_nodetypes.cnd │ │ │ ├── persistence/ │ │ │ │ ├── bundle/ │ │ │ │ │ ├── azure.ddl │ │ │ │ │ ├── db2.ddl │ │ │ │ │ ├── derby.ddl │ │ │ │ │ ├── h2.ddl │ │ │ │ │ ├── ingres.ddl │ │ │ │ │ ├── mssql.ddl │ │ │ │ │ ├── mysql.ddl │ │ │ │ │ ├── oracle.ddl │ │ │ │ │ └── postgresql.ddl │ │ │ │ └── db/ │ │ │ │ ├── azure.ddl │ │ │ │ ├── daffodil.ddl │ │ │ │ ├── db2.ddl │ │ │ │ ├── default.ddl │ │ │ │ ├── derby.ddl │ │ │ │ ├── h2.ddl │ │ │ │ ├── ingres.ddl │ │ │ │ ├── maxdb.ddl │ │ │ │ ├── mssql.ddl │ │ │ │ ├── mysql.ddl │ │ │ │ ├── oracle.ddl │ │ │ │ └── postgresql.ddl │ │ │ ├── query/ │ │ │ │ └── lucene/ │ │ │ │ ├── indexing-configuration-1.0.dtd │ │ │ │ ├── indexing-configuration-1.1.dtd │ │ │ │ ├── indexing-configuration-1.2.dtd │ │ │ │ └── tika-config.xml │ │ │ ├── repository.xml │ │ │ └── test-nodetypes.xml │ │ └── resources-filtered/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── core/ │ │ └── repository.properties │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ ├── api/ │ │ │ ├── JackrabbitNodeTest.java │ │ │ ├── JackrabbitObservationManagerTest.java │ │ │ ├── JackrabbitSessionTest.java │ │ │ ├── TestAll.java │ │ │ └── security/ │ │ │ ├── JackrabbitAccessControlManagerTest.java │ │ │ ├── TestAll.java │ │ │ ├── authorization/ │ │ │ │ ├── PrivilegeManagerTest.java │ │ │ │ └── TestAll.java │ │ │ ├── principal/ │ │ │ │ ├── PrincipalManagerTest.java │ │ │ │ └── TestAll.java │ │ │ └── user/ │ │ │ ├── AbstractUserTest.java │ │ │ ├── AuthorizableTest.java │ │ │ ├── ConcurrentCreateUserTest.java │ │ │ ├── GroupTest.java │ │ │ ├── ImpersonationTest.java │ │ │ ├── NestedGroupTest.java │ │ │ ├── TestAll.java │ │ │ ├── UserManagerCreateGroupTest.java │ │ │ ├── UserManagerCreateUserTest.java │ │ │ ├── UserManagerSearchTest.java │ │ │ ├── UserManagerTest.java │ │ │ └── UserTest.java │ │ └── core/ │ │ ├── AbstractConcurrencyTest.java │ │ ├── AddMoveTest.java │ │ ├── CachingHierarchyManagerTest.java │ │ ├── ConcurrencyTest.java │ │ ├── ConcurrencyTest3.java │ │ ├── ConcurrentAddMoveRemoveTest.java │ │ ├── ConcurrentAddRemoveMoveTest.java │ │ ├── ConcurrentAddRemoveNodeTest.java │ │ ├── ConcurrentAddRemovePropertyTest.java │ │ ├── ConcurrentCheckinMixedTransactionTest.java │ │ ├── ConcurrentCyclicMoveTest.java │ │ ├── ConcurrentImportTest.java │ │ ├── ConcurrentLoginTest.java │ │ ├── ConcurrentMixinModificationTest.java │ │ ├── ConcurrentModificationBase.java │ │ ├── ConcurrentModificationWithSNSTest.java │ │ ├── ConcurrentMoveTest.java │ │ ├── ConcurrentNodeModificationTest.java │ │ ├── ConcurrentReadWriteTest.java │ │ ├── ConcurrentRenameTest.java │ │ ├── ConcurrentReorderTest.java │ │ ├── ConcurrentSaveTest.java │ │ ├── ConcurrentVersioningTest.java │ │ ├── ConcurrentVersioningWithTransactionsTest.java │ │ ├── ConcurrentWorkspaceCopyTest.java │ │ ├── ConsistencyCheck.java │ │ ├── InvalidDateTest.java │ │ ├── LockTest.java │ │ ├── LockedWrapperTest.java │ │ ├── LostFromCacheIssueTest.java │ │ ├── MoveAtRootTest.java │ │ ├── MoveRemoveTest.java │ │ ├── MoveTest.java │ │ ├── MultiWorkspaceShareableNodeTest.java │ │ ├── NPEandCMETest.java │ │ ├── NodeImplTest.java │ │ ├── OracleRepositoryTest.java │ │ ├── OracleRetrocompatibleRepositoryTest.java │ │ ├── OverlappingNodeAddTest.java │ │ ├── ReadVersionsWhileModified.java │ │ ├── ReadWhileSaveTest.java │ │ ├── ReferencesTest.java │ │ ├── RemoveAddNodeWithUUIDTest.java │ │ ├── ReplacePropertyWhileOthersReadTest.java │ │ ├── ReplaceTest.java │ │ ├── RepositoryCopierTest.java │ │ ├── RestoreAndCheckoutTest.java │ │ ├── RetentionRegistryImplTest.java │ │ ├── SessionGarbageCollectedTest.java │ │ ├── ShareableNodeTest.java │ │ ├── Tail.java │ │ ├── TestAll.java │ │ ├── TestHelper.java │ │ ├── TestRepository.java │ │ ├── TransientRepositoryTest.java │ │ ├── UserPerWorkspaceSecurityManagerTest.java │ │ ├── UserTransactionImpl.java │ │ ├── XATest.java │ │ ├── cache/ │ │ │ ├── ConcurrentCacheTest.java │ │ │ └── GrowingLRUMapTest.java │ │ ├── cluster/ │ │ │ ├── ClusterDescriptorTest.java │ │ │ ├── ClusterRecordTest.java │ │ │ ├── ClusterSyncTest.java │ │ │ ├── DbClusterTest.java │ │ │ ├── DbClusterTestJCR3162.java │ │ │ ├── FailUpdateOnJournalExceptionTest.java │ │ │ ├── SimpleClusterContext.java │ │ │ ├── SimpleEventListener.java │ │ │ ├── TestAll.java │ │ │ ├── TestJournal.java │ │ │ └── UpdateEventFactory.java │ │ ├── config/ │ │ │ ├── DataSourceConfigTest.java │ │ │ ├── RepositoryConfigTest.java │ │ │ ├── SecurityConfigTest.java │ │ │ ├── TestAll.java │ │ │ └── WorkspaceConfigTest.java │ │ ├── data/ │ │ │ ├── ConcurrentGcTest.java │ │ │ ├── ConsistencyCheckerImplTest.java │ │ │ ├── CopyValueTest.java │ │ │ ├── DBDataStoreTest.java │ │ │ ├── DataStoreAPITest.java │ │ │ ├── DataStoreTest.java │ │ │ ├── ExportImportTest.java │ │ │ ├── GCConcurrentTest.java │ │ │ ├── GCEventListenerTest.java │ │ │ ├── GCSubtreeMoveTest.java │ │ │ ├── GCThread.java │ │ │ ├── GarbageCollectorTest.java │ │ │ ├── LazyFileInputStreamTest.java │ │ │ ├── NodeTypeTest.java │ │ │ ├── OpenFilesTest.java │ │ │ ├── PersistenceManagerIteratorTest.java │ │ │ ├── TestAll.java │ │ │ ├── TestTwoGetStreams.java │ │ │ └── WriteWhileReadingTest.java │ │ ├── fs/ │ │ │ ├── AbstractFileSystemTest.java │ │ │ ├── TestAll.java │ │ │ ├── db/ │ │ │ │ ├── DerbyFileSystemTest.java │ │ │ │ ├── OracleFileSystemTest.java │ │ │ │ └── OracleRetrocompatibleFileSystemTest.java │ │ │ ├── local/ │ │ │ │ └── LocalFileSystemTest.java │ │ │ └── mem/ │ │ │ └── MemoryFileSystemTest.java │ │ ├── id/ │ │ │ ├── NodeIdFactoryTest.java │ │ │ ├── NodeIdTest.java │ │ │ └── TestAll.java │ │ ├── integration/ │ │ │ ├── AxisQueryTest.java │ │ │ ├── CachingHierarchyManagerConsistencyTest.java │ │ │ ├── ConcurrentQueriesWithUpdatesTest.java │ │ │ ├── ConcurrentQueryTest.java │ │ │ ├── GQLTest.java │ │ │ ├── GetOrNullTest.java │ │ │ ├── InterruptedQueryTest.java │ │ │ ├── ItemSequenceTest.java │ │ │ ├── JCRAPITest.java │ │ │ ├── JCRBenchmark.java │ │ │ ├── MassiveRangeTest.java │ │ │ ├── MassiveWildcardTest.java │ │ │ ├── NodeImplTest.java │ │ │ ├── RepositoryFactoryImplTest.java │ │ │ ├── RepositoryLockTest.java │ │ │ ├── RestoreSameNameSiblingTest.java │ │ │ ├── SessionImplTest.java │ │ │ ├── TreeTraverserTest.java │ │ │ ├── UtilsGetPathTest.java │ │ │ ├── VersioningTest.java │ │ │ ├── WorkspaceInitTest.java │ │ │ ├── benchmark/ │ │ │ │ ├── ItemStateCacheSyncTest.java │ │ │ │ └── SimpleBench.java │ │ │ ├── daily/ │ │ │ │ ├── DailyIntegrationTest.java │ │ │ │ ├── ItemStateHierarchyManagerDeadlockTest.java │ │ │ │ └── RandomOperationTest.java │ │ │ └── random/ │ │ │ ├── operation/ │ │ │ │ ├── AddNode.java │ │ │ │ ├── AddVersionLabel.java │ │ │ │ ├── Checkin.java │ │ │ │ ├── Checkout.java │ │ │ │ ├── CreateNodes.java │ │ │ │ ├── GetNode.java │ │ │ │ ├── GetRandomNodes.java │ │ │ │ ├── Operation.java │ │ │ │ ├── OperationFactory.java │ │ │ │ ├── OperationSequence.java │ │ │ │ ├── RandomContentOperations.java │ │ │ │ ├── RandomOperations.java │ │ │ │ ├── RandomVersionOperations.java │ │ │ │ ├── Remove.java │ │ │ │ ├── RemoveVersion.java │ │ │ │ ├── RemoveVersionLabel.java │ │ │ │ ├── Restore.java │ │ │ │ ├── Save.java │ │ │ │ ├── SetProperty.java │ │ │ │ ├── TraverseNodes.java │ │ │ │ ├── VersionOperation.java │ │ │ │ └── XATransaction.java │ │ │ └── task/ │ │ │ ├── ContentOperationsTask.java │ │ │ ├── RandomOperationsTask.java │ │ │ └── VersionOperationsTask.java │ │ ├── journal/ │ │ │ ├── FileJournalTest.java │ │ │ ├── LockableFileRevisionTest.java │ │ │ └── TestAll.java │ │ ├── lock/ │ │ │ ├── ConcurrentLockingTest.java │ │ │ ├── ConcurrentLockingWithTransactionsTest.java │ │ │ ├── ExtendedLockingTest.java │ │ │ ├── LockTimeoutTest.java │ │ │ └── TestAll.java │ │ ├── nodetype/ │ │ │ ├── CyclicNodeTypeRegistrationTest.java │ │ │ ├── MixinTest.java │ │ │ ├── NodeTypesInContentTest.java │ │ │ ├── TestAll.java │ │ │ └── xml/ │ │ │ ├── SimpleNamespaceRegistry.java │ │ │ └── TestAll.java │ │ ├── observation/ │ │ │ ├── MixinTest.java │ │ │ ├── MoveInPlaceTest.java │ │ │ ├── ReorderTest.java │ │ │ ├── ShareableNodesTest.java │ │ │ ├── TestAll.java │ │ │ ├── VersionEventsTest.java │ │ │ └── WarningOnSaveWithNotificationThreadTest.java │ │ ├── persistence/ │ │ │ ├── AutoFixCorruptNode.java │ │ │ ├── PersistenceManagerTest.java │ │ │ ├── TestAll.java │ │ │ └── util/ │ │ │ ├── BundleBindingRandomizedTest.java │ │ │ ├── BundleBindingTest.java │ │ │ ├── HashMapIndexTest.java │ │ │ ├── NodeCorruptionTest.java │ │ │ └── TestAll.java │ │ ├── query/ │ │ │ ├── AbstractIndexingTest.java │ │ │ ├── AbstractQueryTest.java │ │ │ ├── ChildAxisQueryTest.java │ │ │ ├── DerefTest.java │ │ │ ├── DescendantSelfAxisTest.java │ │ │ ├── ExcerptTest.java │ │ │ ├── FnNameQueryTest.java │ │ │ ├── FulltextQueryTest.java │ │ │ ├── FulltextSQL2QueryTest.java │ │ │ ├── JoinTest.java │ │ │ ├── LazyResultSetQueryTest.java │ │ │ ├── LimitAndOffsetTest.java │ │ │ ├── LimitedAccessQueryTest.java │ │ │ ├── MixinTest.java │ │ │ ├── OrderByTest.java │ │ │ ├── ParentNodeTest.java │ │ │ ├── PathQueryNodeTest.java │ │ │ ├── QueryExtensionsTest.java │ │ │ ├── QueryResultTest.java │ │ │ ├── SQL2NodeLocalNameTest.java │ │ │ ├── SQL2OffsetLimitTest.java │ │ │ ├── SQL2OrderByTest.java │ │ │ ├── SQL2OuterJoinTest.java │ │ │ ├── SQL2PathEscapingTest.java │ │ │ ├── SQL2QueryResultTest.java │ │ │ ├── SQL2TooManyClausesTest.java │ │ │ ├── SQLTest.java │ │ │ ├── SelectClauseTest.java │ │ │ ├── ShareableNodeTest.java │ │ │ ├── SimilarQueryTest.java │ │ │ ├── SimpleQueryTest.java │ │ │ ├── SkipDeletedNodesTest.java │ │ │ ├── SkipDeniedNodesTest.java │ │ │ ├── TestAll.java │ │ │ ├── TextExtractorTest.java │ │ │ ├── UpperLowerCaseQueryTest.java │ │ │ ├── VersionStoreQueryTest.java │ │ │ ├── XPathAxisTest.java │ │ │ └── lucene/ │ │ │ ├── BlockingParser.java │ │ │ ├── ChainedTermEnumTest.java │ │ │ ├── ComparableArrayTest.java │ │ │ ├── DecimalConvertTest.java │ │ │ ├── IDFieldTest.java │ │ │ ├── IndexFormatVersionTest.java │ │ │ ├── IndexInfosTest.java │ │ │ ├── IndexMigrationTest.java │ │ │ ├── IndexingAggregateTest.java │ │ │ ├── IndexingConfigurationImplTest.java │ │ │ ├── IndexingQueueTest.java │ │ │ ├── IndexingRuleTest.java │ │ │ ├── LargeResultSetTest.java │ │ │ ├── LazyTextExtractorFieldTest.java │ │ │ ├── SQL2IndexingAggregateTest.java │ │ │ ├── SQL2IndexingAggregateTest2.java │ │ │ ├── SearchIndexConsistencyCheckTest.java │ │ │ ├── SearchIndexTest.java │ │ │ ├── SlowQueryHandler.java │ │ │ ├── SynonymProviderTest.java │ │ │ ├── TestAll.java │ │ │ ├── TextExtractionQueryTest.java │ │ │ ├── UtilTest.java │ │ │ ├── directory/ │ │ │ │ ├── DirectoryManagerTest.java │ │ │ │ ├── IndexInputStreamTest.java │ │ │ │ ├── IndexOutputStreamTest.java │ │ │ │ └── TestAll.java │ │ │ └── hits/ │ │ │ └── ArrayHitsTest.java │ │ ├── retention/ │ │ │ ├── AbstractRetentionTest.java │ │ │ ├── HoldTest.java │ │ │ ├── RetentionPolicyTest.java │ │ │ └── TestAll.java │ │ ├── security/ │ │ │ ├── AccessManagerTest.java │ │ │ ├── TestAll.java │ │ │ ├── TestPrincipal.java │ │ │ ├── authentication/ │ │ │ │ ├── CryptedSimpleCredentialsTest.java │ │ │ │ ├── DefaultLoginModuleTest.java │ │ │ │ ├── GuestLoginTest.java │ │ │ │ ├── LoginModuleTest.java │ │ │ │ ├── NullLoginTest.java │ │ │ │ ├── SimpleCredentialsAuthenticationTest.java │ │ │ │ ├── TestAll.java │ │ │ │ └── token/ │ │ │ │ ├── CompatTokenProviderTest.java │ │ │ │ ├── TestAll.java │ │ │ │ ├── TokenBasedAuthenticationCompatTest.java │ │ │ │ ├── TokenBasedAuthenticationTest.java │ │ │ │ ├── TokenBasedLoginTest.java │ │ │ │ └── TokenProviderTest.java │ │ │ ├── authorization/ │ │ │ │ ├── AbstractACLTemplateTest.java │ │ │ │ ├── AbstractEffectivePolicyTest.java │ │ │ │ ├── AbstractEntryTest.java │ │ │ │ ├── AbstractEvaluationTest.java │ │ │ │ ├── AbstractLockManagementTest.java │ │ │ │ ├── AbstractNodeTypeManagementTest.java │ │ │ │ ├── AbstractRepositoryOperationTest.java │ │ │ │ ├── AbstractVersionManagementTest.java │ │ │ │ ├── AbstractWriteTest.java │ │ │ │ ├── CustomPrivilegeTest.java │ │ │ │ ├── GlobPatternTest.java │ │ │ │ ├── JackrabbitAccessControlListTest.java │ │ │ │ ├── PermissionTest.java │ │ │ │ ├── PrivilegeBitsTest.java │ │ │ │ ├── PrivilegeManagerImplTest.java │ │ │ │ ├── PrivilegeRegistryTest.java │ │ │ │ ├── TestAll.java │ │ │ │ ├── acl/ │ │ │ │ │ ├── ACLEditorTest.java │ │ │ │ │ ├── ACLTemplateEntryTest.java │ │ │ │ │ ├── ACLTemplateTest.java │ │ │ │ │ ├── AcReadWriteTest.java │ │ │ │ │ ├── EffectivePolicyTest.java │ │ │ │ │ ├── EntryCollectorTest.java │ │ │ │ │ ├── EntryTest.java │ │ │ │ │ ├── EvaluationUtil.java │ │ │ │ │ ├── LockTest.java │ │ │ │ │ ├── MoveTest.java │ │ │ │ │ ├── NodeTypeTest.java │ │ │ │ │ ├── ReadNodeTypeTest.java │ │ │ │ │ ├── ReadTest.java │ │ │ │ │ ├── RepositoryOperationTest.java │ │ │ │ │ ├── RestrictionTest.java │ │ │ │ │ ├── TestAll.java │ │ │ │ │ ├── VersionTest.java │ │ │ │ │ └── WriteTest.java │ │ │ │ ├── combined/ │ │ │ │ │ ├── TestAll.java │ │ │ │ │ └── WriteTest.java │ │ │ │ └── principalbased/ │ │ │ │ ├── ACLTemplateTest.java │ │ │ │ ├── EffectivePolicyTest.java │ │ │ │ ├── EntryTest.java │ │ │ │ ├── EvaluationUtil.java │ │ │ │ ├── LockTest.java │ │ │ │ ├── NodeTypeTest.java │ │ │ │ ├── RepositoryOperationTest.java │ │ │ │ ├── TestAll.java │ │ │ │ ├── VersionTest.java │ │ │ │ └── WriteTest.java │ │ │ ├── principal/ │ │ │ │ ├── AbstractPrincipalProviderTest.java │ │ │ │ ├── EveryonePrincipalTest.java │ │ │ │ ├── PrincipalManagerTest.java │ │ │ │ └── TestAll.java │ │ │ ├── simple/ │ │ │ │ ├── SimpleSecurityManagerTest.java │ │ │ │ └── TestAll.java │ │ │ └── user/ │ │ │ ├── AdministratorTest.java │ │ │ ├── AuthorizableActionTest.java │ │ │ ├── AuthorizableImplTest.java │ │ │ ├── DefaultPrincipalProviderTest.java │ │ │ ├── GroupAdministratorTest.java │ │ │ ├── GroupImplTest.java │ │ │ ├── ImpersonationImplTest.java │ │ │ ├── IndexNodeResolverTest.java │ │ │ ├── MembershipCachePerfTest.java │ │ │ ├── MembershipCacheTest.java │ │ │ ├── NodeCreationTest.java │ │ │ ├── NodeResolverTest.java │ │ │ ├── NotUserAdministratorTest.java │ │ │ ├── PasswordUtilityTest.java │ │ │ ├── TestAll.java │ │ │ ├── TraversingNodeResolverTest.java │ │ │ ├── UserAccessControlProviderTest.java │ │ │ ├── UserAdministratorTest.java │ │ │ ├── UserImplTest.java │ │ │ ├── UserImporterTest.java │ │ │ └── UserManagerImplTest.java │ │ ├── state/ │ │ │ ├── AbstractISMLockingTest.java │ │ │ ├── ChangeLogTest.java │ │ │ ├── DefaultISMLockingDeadlockTest.java │ │ │ ├── DefaultISMLockingTest.java │ │ │ ├── FineGrainedISMLockingTest.java │ │ │ ├── NameSetTest.java │ │ │ ├── NodeStateMergerTest.java │ │ │ └── TestAll.java │ │ ├── stats/ │ │ │ ├── QueryStatCoreTest.java │ │ │ └── TestAll.java │ │ ├── util/ │ │ │ ├── CooperativeFileLockTest.java │ │ │ ├── DOMWalkerTest.java │ │ │ ├── RepositoryLockTest.java │ │ │ ├── TestAll.java │ │ │ └── db/ │ │ │ ├── ConnectionFactoryTest.java │ │ │ └── TestAll.java │ │ ├── value/ │ │ │ ├── BinaryValueTest.java │ │ │ ├── InternalValueFactoryTest.java │ │ │ ├── InternalValueTest.java │ │ │ ├── PathTest.java │ │ │ ├── ReferenceBinaryTest.java │ │ │ └── TestAll.java │ │ ├── version/ │ │ │ ├── CheckinRemoveVersionTest.java │ │ │ ├── CopyFrozenUuidTest.java │ │ │ ├── InternalVersionHistoryImplTest.java │ │ │ ├── ModifyNonVersionableCheckedOutTest.java │ │ │ ├── RemoveAndAddVersionLabelXATest.java │ │ │ ├── RemoveOrphanVersionHistoryTest.java │ │ │ ├── RemoveVersionLabelTest.java │ │ │ ├── RemoveVersionTest.java │ │ │ ├── RestoreNodeWithSNSTest.java │ │ │ ├── RestoreTest.java │ │ │ ├── TestAll.java │ │ │ └── VersionIteratorImplTest.java │ │ └── xml/ │ │ ├── AccessControlImporterTest.java │ │ ├── DocumentViewTest.java │ │ ├── SystemViewTest.java │ │ ├── TestAll.java │ │ └── WorkspaceImporterTest.java │ ├── repository/ │ │ ├── repository.xml │ │ └── workspaces/ │ │ ├── default/ │ │ │ ├── synonyms.properties │ │ │ └── workspace.xml │ │ ├── index-format-v1/ │ │ │ ├── index/ │ │ │ │ ├── _0/ │ │ │ │ │ ├── _2.cfs │ │ │ │ │ ├── deletable │ │ │ │ │ └── segments │ │ │ │ └── indexes │ │ │ ├── items/ │ │ │ │ ├── 5a/ │ │ │ │ │ └── 9a/ │ │ │ │ │ └── d0fcc7f542bbb435bcb9ed30a2e2.n │ │ │ │ ├── ca/ │ │ │ │ │ └── fe/ │ │ │ │ │ └── babecafebabecafebabecafebabe.n │ │ │ │ └── de/ │ │ │ │ └── ad/ │ │ │ │ └── beefcafebabecafebabecafebabe.n │ │ │ ├── names.properties │ │ │ ├── namespaces.properties │ │ │ └── workspace.xml │ │ ├── index-format-v2/ │ │ │ ├── index/ │ │ │ │ ├── _0/ │ │ │ │ │ ├── _0.cfs │ │ │ │ │ ├── segments.gen │ │ │ │ │ ├── segments_1 │ │ │ │ │ └── segments_3 │ │ │ │ └── indexes │ │ │ ├── items/ │ │ │ │ ├── c9/ │ │ │ │ │ └── bb/ │ │ │ │ │ └── 26c0edf0408b8ab22e88c1edc593.n │ │ │ │ ├── ca/ │ │ │ │ │ └── fe/ │ │ │ │ │ └── babecafebabecafebabecafebabe.n │ │ │ │ └── de/ │ │ │ │ └── ad/ │ │ │ │ └── beefcafebabecafebabecafebabe.n │ │ │ ├── names.properties │ │ │ ├── namespaces.properties │ │ │ └── workspace.xml │ │ ├── index-format-v3/ │ │ │ ├── items/ │ │ │ │ ├── c9/ │ │ │ │ │ └── bb/ │ │ │ │ │ └── 26c0edf0408b8ab22e88c1edc593.n │ │ │ │ ├── ca/ │ │ │ │ │ └── fe/ │ │ │ │ │ └── babecafebabecafebabecafebabe.n │ │ │ │ └── de/ │ │ │ │ └── ad/ │ │ │ │ └── beefcafebabecafebabecafebabe.n │ │ │ ├── names.properties │ │ │ ├── namespaces.properties │ │ │ └── workspace.xml │ │ ├── indexing-test/ │ │ │ ├── indexing-configuration.xml │ │ │ └── workspace.xml │ │ ├── indexing-test-2/ │ │ │ ├── indexing-configuration.xml │ │ │ └── workspace.xml │ │ ├── security/ │ │ │ └── workspace.xml │ │ └── wsp-init-test/ │ │ └── workspace.xml │ ├── repository-descriptor-overlay/ │ │ ├── repository.xml │ │ └── workspaces/ │ │ ├── default/ │ │ │ └── workspace.xml │ │ ├── index-format-v1/ │ │ │ └── workspace.xml │ │ ├── index-format-v2/ │ │ │ └── workspace.xml │ │ ├── index-format-v3/ │ │ │ └── workspace.xml │ │ ├── indexing-test/ │ │ │ └── workspace.xml │ │ ├── security/ │ │ │ └── workspace.xml │ │ └── wsp-init-test/ │ │ └── workspace.xml │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.apache.tika.parser.Parser │ ├── cnd-reader-test-input.cnd │ ├── jaas.config │ ├── logback-test.xml │ ├── org/ │ │ └── apache/ │ │ ├── jackrabbit/ │ │ │ ├── api/ │ │ │ │ └── test_mixin_nodetypes.cnd │ │ │ └── core/ │ │ │ ├── cluster/ │ │ │ │ ├── repository-h2.xml │ │ │ │ ├── repository-with-test-journal.xml │ │ │ │ └── repository.xml │ │ │ ├── config/ │ │ │ │ └── workspace.xml │ │ │ ├── integration/ │ │ │ │ ├── repository-with-SimpleFSDirectory.xml │ │ │ │ └── words.txt │ │ │ ├── nodetype/ │ │ │ │ └── xml/ │ │ │ │ ├── nodetypes.dtd │ │ │ │ ├── test_mixin_nodetypes.cnd │ │ │ │ ├── test_nodestatemerger_nodetypes.cnd │ │ │ │ ├── test_nodetypes.xml │ │ │ │ ├── test_ns_cnd_nodetypes.cnd │ │ │ │ ├── test_ns_xml_nodetypes.xml │ │ │ │ ├── test_same_nt_name_cnd_nodetypes.cnd │ │ │ │ └── test_same_nt_name_xml_nodetypes.xml │ │ │ ├── query/ │ │ │ │ ├── lucene/ │ │ │ │ │ ├── indexing_config1.xml │ │ │ │ │ ├── indexing_config2.xml │ │ │ │ │ ├── indexing_config3.xml │ │ │ │ │ ├── indexing_config4.xml │ │ │ │ │ ├── indexing_config5.xml │ │ │ │ │ └── indexing_config6.xml │ │ │ │ ├── test.rtf │ │ │ │ └── test.txt │ │ │ ├── repository-oracle-compat.xml │ │ │ ├── repository-oracle.xml │ │ │ └── security/ │ │ │ ├── simple/ │ │ │ │ └── simple_repository.xml │ │ │ └── user/ │ │ │ ├── repository-membersplit.xml │ │ │ └── repository.xml │ │ └── tika/ │ │ └── mime/ │ │ └── custom-mimetypes.xml │ ├── repositoryHelperPool.properties │ ├── repositoryStubImpl.properties │ ├── xsd-converter-model-output.cnd │ └── xsd-converter-test-input.xsd ├── jackrabbit-data/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ ├── core/ │ │ │ │ ├── config/ │ │ │ │ │ ├── ConfigurationException.java │ │ │ │ │ ├── DataSourceConfig.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── data/ │ │ │ │ │ ├── AbstractBackend.java │ │ │ │ │ ├── AbstractDataRecord.java │ │ │ │ │ ├── AbstractDataStore.java │ │ │ │ │ ├── AsyncTouchCallback.java │ │ │ │ │ ├── AsyncTouchResult.java │ │ │ │ │ ├── AsyncUploadCache.java │ │ │ │ │ ├── AsyncUploadCacheResult.java │ │ │ │ │ ├── AsyncUploadCallback.java │ │ │ │ │ ├── AsyncUploadResult.java │ │ │ │ │ ├── Backend.java │ │ │ │ │ ├── BackendResourceAbortable.java │ │ │ │ │ ├── CachingDataRecord.java │ │ │ │ │ ├── CachingDataStore.java │ │ │ │ │ ├── CachingFDS.java │ │ │ │ │ ├── DataIdentifier.java │ │ │ │ │ ├── DataRecord.java │ │ │ │ │ ├── DataStore.java │ │ │ │ │ ├── DataStoreException.java │ │ │ │ │ ├── DataStoreFactory.java │ │ │ │ │ ├── FSBackend.java │ │ │ │ │ ├── FileDataRecord.java │ │ │ │ │ ├── FileDataStore.java │ │ │ │ │ ├── LazyFileInputStream.java │ │ │ │ │ ├── LocalCache.java │ │ │ │ │ ├── MultiDataStore.java │ │ │ │ │ ├── MultiDataStoreAware.java │ │ │ │ │ ├── ScanEventListener.java │ │ │ │ │ ├── db/ │ │ │ │ │ │ ├── DbDataRecord.java │ │ │ │ │ │ ├── DbDataStore.java │ │ │ │ │ │ ├── DbInputStream.java │ │ │ │ │ │ ├── DerbyDataStore.java │ │ │ │ │ │ ├── ResettableTempFileInputStream.java │ │ │ │ │ │ ├── TempFileInputStream.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── util/ │ │ │ │ │ ├── NamedThreadFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── fs/ │ │ │ │ │ ├── BasedFileSystem.java │ │ │ │ │ ├── FileSystem.java │ │ │ │ │ ├── FileSystemException.java │ │ │ │ │ ├── FileSystemFactory.java │ │ │ │ │ ├── FileSystemPathUtil.java │ │ │ │ │ ├── FileSystemResource.java │ │ │ │ │ ├── RandomAccessOutputStream.java │ │ │ │ │ ├── local/ │ │ │ │ │ │ ├── FileUtil.java │ │ │ │ │ │ ├── HandleMonitor.java │ │ │ │ │ │ ├── LocalFileSystem.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── util/ │ │ │ │ └── db/ │ │ │ │ ├── CheckSchemaOperation.java │ │ │ │ ├── ConnectionFactory.java │ │ │ │ ├── ConnectionHelper.java │ │ │ │ ├── DataSourceWrapper.java │ │ │ │ ├── DatabaseAware.java │ │ │ │ ├── DbUtility.java │ │ │ │ ├── DerbyConnectionHelper.java │ │ │ │ ├── Oracle10R1ConnectionHelper.java │ │ │ │ ├── OracleConnectionHelper.java │ │ │ │ ├── PostgreSQLConnectionHelper.java │ │ │ │ ├── ResultSetWrapper.java │ │ │ │ ├── StreamWrapper.java │ │ │ │ └── package-info.java │ │ │ └── data/ │ │ │ └── core/ │ │ │ ├── InternalXAResource.java │ │ │ ├── TransactionContext.java │ │ │ ├── TransactionException.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── core/ │ │ └── data/ │ │ └── db/ │ │ ├── azure.properties │ │ ├── db2.properties │ │ ├── derby.properties │ │ ├── h2.properties │ │ ├── ingres.properties │ │ ├── mssql.properties │ │ ├── mysql.properties │ │ ├── oracle.properties │ │ ├── postgresql.properties │ │ └── sqlserver.properties │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── core/ │ │ └── data/ │ │ ├── InMemoryBackend.java │ │ ├── InMemoryDataStore.java │ │ ├── RandomInputStream.java │ │ ├── TestCachingFDS.java │ │ ├── TestCachingFDSCacheOff.java │ │ ├── TestCaseBase.java │ │ ├── TestFileDataStore.java │ │ ├── TestInMemDs.java │ │ ├── TestInMemDsCacheOff.java │ │ ├── TestLocalCache.java │ │ └── db/ │ │ ├── ResettableTempFileInputStreamTest.java │ │ └── TempFileInputStreamTest.java │ └── resources/ │ ├── fs.properties │ └── log4j.properties ├── jackrabbit-it-osgi/ │ ├── README.md │ ├── pom.xml │ ├── src/ │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── osgi/ │ │ │ ├── OSGiIT.java │ │ │ └── slf4j2/ │ │ │ ├── Slf4j_v2_Tika_v2_4_OSGiIT.java │ │ │ └── Slf4j_v2_Tika_v2_9_OSGiIT.java │ │ └── resources/ │ │ ├── exam.properties │ │ └── logback-test.xml │ └── test-bundles.xml ├── jackrabbit-jca/ │ ├── HEADER.txt │ ├── README.txt │ ├── deploy/ │ │ ├── geronimo/ │ │ │ └── geronimo-ra.xml │ │ ├── jboss/ │ │ │ └── jcr-ds.xml │ │ └── weblogic/ │ │ └── weblogic-ra.xml │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── jca/ │ │ │ ├── AnonymousConnection.java │ │ │ ├── JCAConnectionManager.java │ │ │ ├── JCAConnectionRequestInfo.java │ │ │ ├── JCAManagedConnection.java │ │ │ ├── JCAManagedConnectionFactory.java │ │ │ ├── JCARepositoryHandle.java │ │ │ ├── JCARepositoryManager.java │ │ │ ├── JCAResourceAdapter.java │ │ │ ├── JCASessionHandle.java │ │ │ └── TransactionBoundXAResource.java │ │ ├── rar/ │ │ │ └── META-INF/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ └── ra.xml │ │ ├── rar10/ │ │ │ └── META-INF/ │ │ │ └── ra.xml │ │ └── resources/ │ │ └── logback.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── jca/ │ │ └── test/ │ │ ├── AbstractTestCase.java │ │ ├── ConnectionFactoryTest.java │ │ └── ConnectionRequestInfoTest.java │ └── resources/ │ └── logback-test.xml ├── jackrabbit-jcr-client/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── client/ │ │ │ └── RepositoryFactoryImpl.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── javax.jcr.RepositoryFactory │ └── test/ │ └── java/ │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── client/ │ ├── RepositoryFactoryImplTest.java │ └── RepositoryFactoryTest.java ├── jackrabbit-jcr-commons/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ └── NOTICE │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ ├── JcrConstants.java │ │ │ ├── commons/ │ │ │ │ ├── AbstractItem.java │ │ │ │ ├── AbstractNode.java │ │ │ │ ├── AbstractProperty.java │ │ │ │ ├── AbstractRepository.java │ │ │ │ ├── AbstractSession.java │ │ │ │ ├── AbstractWorkspace.java │ │ │ │ ├── GenericRepositoryFactory.java │ │ │ │ ├── ItemNameMatcher.java │ │ │ │ ├── JcrUtils.java │ │ │ │ ├── JndiRepositoryFactory.java │ │ │ │ ├── NamespaceHelper.java │ │ │ │ ├── SimpleValueFactory.java │ │ │ │ ├── cnd/ │ │ │ │ │ ├── CndImporter.java │ │ │ │ │ ├── CompactNodeTypeDefReader.java │ │ │ │ │ ├── CompactNodeTypeDefWriter.java │ │ │ │ │ ├── DefinitionBuilderFactory.java │ │ │ │ │ ├── Lexer.java │ │ │ │ │ ├── ParseException.java │ │ │ │ │ ├── TemplateBuilderFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── flat/ │ │ │ │ │ ├── BTreeManager.java │ │ │ │ │ ├── FilterIterator.java │ │ │ │ │ ├── ItemSequence.java │ │ │ │ │ ├── NodeSequence.java │ │ │ │ │ ├── PropertySequence.java │ │ │ │ │ ├── Rank.java │ │ │ │ │ ├── Sequence.java │ │ │ │ │ ├── SizedIterator.java │ │ │ │ │ ├── TreeManager.java │ │ │ │ │ ├── TreeTraverser.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── iterator/ │ │ │ │ │ ├── AbstractLazyIterator.java │ │ │ │ │ ├── AccessControlPolicyIteratorAdapter.java │ │ │ │ │ ├── EventIteratorAdapter.java │ │ │ │ │ ├── EventListenerIteratorAdapter.java │ │ │ │ │ ├── FilterIterator.java │ │ │ │ │ ├── FilteredRangeIterator.java │ │ │ │ │ ├── FilteringNodeIterator.java │ │ │ │ │ ├── FrozenNodeIteratorAdapter.java │ │ │ │ │ ├── LazyIteratorChain.java │ │ │ │ │ ├── NodeIterable.java │ │ │ │ │ ├── NodeIteratorAdapter.java │ │ │ │ │ ├── NodeTypeIteratorAdapter.java │ │ │ │ │ ├── PropertyIterable.java │ │ │ │ │ ├── PropertyIteratorAdapter.java │ │ │ │ │ ├── RangeIteratorAdapter.java │ │ │ │ │ ├── RangeIteratorDecorator.java │ │ │ │ │ ├── RowIterable.java │ │ │ │ │ ├── RowIteratorAdapter.java │ │ │ │ │ ├── SizedIterator.java │ │ │ │ │ ├── VersionIteratorAdapter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jackrabbit/ │ │ │ │ │ ├── SimpleReferenceBinary.java │ │ │ │ │ ├── authorization/ │ │ │ │ │ │ ├── AccessControlUtils.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── user/ │ │ │ │ │ ├── AuthorizableQueryManager.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── json/ │ │ │ │ │ ├── JsonHandler.java │ │ │ │ │ ├── JsonParser.java │ │ │ │ │ ├── JsonUtil.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── observation/ │ │ │ │ │ ├── EventTracker.java │ │ │ │ │ ├── JackrabbitEventTracker.java │ │ │ │ │ ├── ListenerTracker.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── packaging/ │ │ │ │ │ ├── ContentPackage.java │ │ │ │ │ ├── ContentPackageExporter.java │ │ │ │ │ ├── FilterContentPackage.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── predicate/ │ │ │ │ │ ├── DeclaringTypePredicate.java │ │ │ │ │ ├── DepthPredicate.java │ │ │ │ │ ├── IsMandatoryPredicate.java │ │ │ │ │ ├── IsNodePredicate.java │ │ │ │ │ ├── NamePredicate.java │ │ │ │ │ ├── NodeTypePredicate.java │ │ │ │ │ ├── NtFilePredicate.java │ │ │ │ │ ├── PathPredicate.java │ │ │ │ │ ├── Predicate.java │ │ │ │ │ ├── Predicates.java │ │ │ │ │ ├── RowPredicate.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── query/ │ │ │ │ │ ├── GQL.java │ │ │ │ │ ├── QueryObjectModelBuilder.java │ │ │ │ │ ├── QueryObjectModelBuilderRegistry.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── qom/ │ │ │ │ │ │ ├── JoinType.java │ │ │ │ │ │ ├── OperandEvaluator.java │ │ │ │ │ │ ├── Operator.java │ │ │ │ │ │ ├── Order.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── sql2/ │ │ │ │ │ ├── Parser.java │ │ │ │ │ ├── QOMFormatter.java │ │ │ │ │ ├── SQL2QOMBuilder.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── repository/ │ │ │ │ │ ├── EmptyRepository.java │ │ │ │ │ ├── JNDIRepository.java │ │ │ │ │ ├── JNDIRepositoryFactory.java │ │ │ │ │ ├── ProxyRepository.java │ │ │ │ │ ├── RepositoryFactory.java │ │ │ │ │ ├── SingletonRepositoryFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── visitor/ │ │ │ │ │ ├── FilteringItemVisitor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── webdav/ │ │ │ │ │ ├── AtomFeedConstants.java │ │ │ │ │ ├── EventUtil.java │ │ │ │ │ ├── JcrRemotingConstants.java │ │ │ │ │ ├── JcrValueType.java │ │ │ │ │ ├── NodeTypeConstants.java │ │ │ │ │ ├── NodeTypeUtil.java │ │ │ │ │ ├── QueryUtil.java │ │ │ │ │ ├── ValueUtil.java │ │ │ │ │ └── package-info.java │ │ │ │ └── xml/ │ │ │ │ ├── DefaultContentHandler.java │ │ │ │ ├── DocumentViewExporter.java │ │ │ │ ├── Exporter.java │ │ │ │ ├── ParsingContentHandler.java │ │ │ │ ├── ProxyContentHandler.java │ │ │ │ ├── SerializingContentHandler.java │ │ │ │ ├── SystemViewExporter.java │ │ │ │ ├── ToXmlContentHandler.java │ │ │ │ ├── XMLFactories.java │ │ │ │ ├── XmlnsContentHandler.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── stats/ │ │ │ │ ├── QueryStatCore.java │ │ │ │ ├── QueryStatDtoComparator.java │ │ │ │ ├── QueryStatDtoImpl.java │ │ │ │ ├── QueryStatDtoOccurrenceComparator.java │ │ │ │ ├── QueryStatImpl.java │ │ │ │ ├── RepositoryStatisticsImpl.java │ │ │ │ ├── TimeSeriesAverage.java │ │ │ │ ├── TimeSeriesMax.java │ │ │ │ ├── TimeSeriesRecorder.java │ │ │ │ ├── TimeSeriesStatsUtil.java │ │ │ │ ├── jmx/ │ │ │ │ │ ├── QueryStatManager.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── util/ │ │ │ │ ├── Base64.java │ │ │ │ ├── ChildrenCollector.java │ │ │ │ ├── ChildrenCollectorFilter.java │ │ │ │ ├── ISO8601.java │ │ │ │ ├── ISO9075.java │ │ │ │ ├── LazyFileInputStream.java │ │ │ │ ├── Locked.java │ │ │ │ ├── LockedWrapper.java │ │ │ │ ├── Text.java │ │ │ │ ├── Timer.java │ │ │ │ ├── TransientFileFactory.java │ │ │ │ ├── WeakIdentityCollection.java │ │ │ │ ├── XMLChar.java │ │ │ │ ├── XMLUtil.java │ │ │ │ └── package-info.java │ │ │ └── value/ │ │ │ ├── AbstractValueFactory.java │ │ │ ├── BaseValue.java │ │ │ ├── BinaryImpl.java │ │ │ ├── BinaryValue.java │ │ │ ├── BooleanValue.java │ │ │ ├── DateValue.java │ │ │ ├── DecimalValue.java │ │ │ ├── DoubleValue.java │ │ │ ├── LongValue.java │ │ │ ├── NameValue.java │ │ │ ├── PathValue.java │ │ │ ├── ReferenceValue.java │ │ │ ├── StringValue.java │ │ │ ├── URIValue.java │ │ │ ├── ValueFactoryImpl.java │ │ │ ├── ValueHelper.java │ │ │ ├── WeakReferenceValue.java │ │ │ └── package-info.java │ │ ├── javadoc/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── uuid/ │ │ │ └── package.html │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ ├── javax.jcr.RepositoryFactory │ │ └── org.apache.jackrabbit.commons.query.QueryObjectModelBuilder │ └── test/ │ └── java/ │ └── org/ │ └── apache/ │ └── jackrabbit/ │ ├── commons/ │ │ ├── AbstractRepositoryTest.java │ │ ├── JcrUtilsTest.java │ │ ├── NamespaceHelperTest.java │ │ ├── SimpleValueFactoryTest.java │ │ ├── flat/ │ │ │ └── RankTest.java │ │ ├── iterator/ │ │ │ └── FilteredRangeIteratorTest.java │ │ ├── json/ │ │ │ ├── JsonParserTest.java │ │ │ ├── JsonUtilTest.java │ │ │ └── TestAll.java │ │ ├── query/ │ │ │ └── GQLTest.java │ │ └── xml/ │ │ ├── ParsingContentHandlerTest.java │ │ ├── SerializingContentHandlerTest.java │ │ ├── ToXmlContentHandlerTest.java │ │ └── XmlnsContentHandlerTest.java │ ├── stats/ │ │ ├── RepositoryStatisticsImplTest.java │ │ ├── TimeSeriesAverageTest.java │ │ ├── TimeSeriesMaxTest.java │ │ └── TimeSeriesRecorderTest.java │ ├── util/ │ │ ├── Base64Test.java │ │ ├── ISO8601Test.java │ │ ├── ISO9075Test.java │ │ ├── JcrUtilsTest.java │ │ ├── TextTest.java │ │ └── TimerTest.java │ ├── value/ │ │ └── BinaryValueTest.java │ └── xml/ │ └── XMLFactoriesTest.java ├── jackrabbit-jcr-server/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ └── NOTICE │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ ├── server/ │ │ │ │ ├── BasicCredentialsProvider.java │ │ │ │ ├── CredentialsProvider.java │ │ │ │ ├── SessionProvider.java │ │ │ │ ├── SessionProviderImpl.java │ │ │ │ ├── io/ │ │ │ │ │ ├── AbstractExportContext.java │ │ │ │ │ ├── BoundedInputStream.java │ │ │ │ │ ├── CopyMoveContext.java │ │ │ │ │ ├── CopyMoveContextImpl.java │ │ │ │ │ ├── CopyMoveHandler.java │ │ │ │ │ ├── CopyMoveManager.java │ │ │ │ │ ├── CopyMoveManagerImpl.java │ │ │ │ │ ├── DefaultHandler.java │ │ │ │ │ ├── DefaultIOListener.java │ │ │ │ │ ├── DefaultIOManager.java │ │ │ │ │ ├── DeleteContext.java │ │ │ │ │ ├── DeleteContextImpl.java │ │ │ │ │ ├── DeleteHandler.java │ │ │ │ │ ├── DeleteManager.java │ │ │ │ │ ├── DeleteManagerImpl.java │ │ │ │ │ ├── DirListingExportHandler.java │ │ │ │ │ ├── ExportContext.java │ │ │ │ │ ├── ExportContextImpl.java │ │ │ │ │ ├── IOContext.java │ │ │ │ │ ├── IOHandler.java │ │ │ │ │ ├── IOListener.java │ │ │ │ │ ├── IOManager.java │ │ │ │ │ ├── IOManagerImpl.java │ │ │ │ │ ├── IOUtil.java │ │ │ │ │ ├── ImportContext.java │ │ │ │ │ ├── ImportContextImpl.java │ │ │ │ │ ├── PropertyExportContext.java │ │ │ │ │ ├── PropertyHandler.java │ │ │ │ │ ├── PropertyImportContext.java │ │ │ │ │ ├── PropertyManager.java │ │ │ │ │ ├── PropertyManagerImpl.java │ │ │ │ │ ├── VersionHandler.java │ │ │ │ │ ├── VersionHistoryHandler.java │ │ │ │ │ ├── XmlHandler.java │ │ │ │ │ └── ZipHandler.java │ │ │ │ ├── jcr/ │ │ │ │ │ └── JCRWebdavServer.java │ │ │ │ ├── package-info.java │ │ │ │ ├── remoting/ │ │ │ │ │ └── davex/ │ │ │ │ │ ├── AclRemoveHandler.java │ │ │ │ │ ├── BatchReadConfig.java │ │ │ │ │ ├── DavexServletService.java │ │ │ │ │ ├── DiffException.java │ │ │ │ │ ├── DiffHandler.java │ │ │ │ │ ├── DiffParser.java │ │ │ │ │ ├── JcrRemotingServlet.java │ │ │ │ │ ├── JsonDiffHandler.java │ │ │ │ │ ├── JsonWriter.java │ │ │ │ │ ├── ProtectedItemRemoveHandler.java │ │ │ │ │ ├── ProtectedRemoveConfig.java │ │ │ │ │ └── ProtectedRemoveManager.java │ │ │ │ └── util/ │ │ │ │ ├── HttpMultipartPost.java │ │ │ │ └── RequestData.java │ │ │ └── webdav/ │ │ │ ├── jcr/ │ │ │ │ ├── AbstractItemResource.java │ │ │ │ ├── AbstractResource.java │ │ │ │ ├── DavLocatorFactoryImpl.java │ │ │ │ ├── DavResourceFactoryImpl.java │ │ │ │ ├── DefaultItemCollection.java │ │ │ │ ├── DefaultItemResource.java │ │ │ │ ├── EventJournalResourceImpl.java │ │ │ │ ├── ItemResourceConstants.java │ │ │ │ ├── JCRWebdavServerServlet.java │ │ │ │ ├── JcrDavException.java │ │ │ │ ├── JcrDavSession.java │ │ │ │ ├── JcrValueType.java │ │ │ │ ├── RootCollection.java │ │ │ │ ├── VersionControlledItemCollection.java │ │ │ │ ├── WorkspaceResourceImpl.java │ │ │ │ ├── lock/ │ │ │ │ │ ├── JcrActiveLock.java │ │ │ │ │ ├── LockTokenMapper.java │ │ │ │ │ └── SessionScopedLockEntry.java │ │ │ │ ├── nodetype/ │ │ │ │ │ ├── ItemDefinitionImpl.java │ │ │ │ │ ├── NodeDefinitionImpl.java │ │ │ │ │ ├── NodeTypeProperty.java │ │ │ │ │ └── PropertyDefinitionImpl.java │ │ │ │ ├── observation/ │ │ │ │ │ ├── SubscriptionImpl.java │ │ │ │ │ └── SubscriptionManagerImpl.java │ │ │ │ ├── property/ │ │ │ │ │ ├── JcrDavPropertyNameSet.java │ │ │ │ │ ├── LengthsProperty.java │ │ │ │ │ ├── NamespacesProperty.java │ │ │ │ │ └── ValuesProperty.java │ │ │ │ ├── search/ │ │ │ │ │ ├── SearchResourceImpl.java │ │ │ │ │ └── SearchResultProperty.java │ │ │ │ ├── security/ │ │ │ │ │ ├── JcrSupportedPrivilegesProperty.java │ │ │ │ │ ├── JcrUserPrivilegesProperty.java │ │ │ │ │ └── SecurityUtils.java │ │ │ │ ├── transaction/ │ │ │ │ │ ├── TransactionListener.java │ │ │ │ │ └── TxLockManagerImpl.java │ │ │ │ └── version/ │ │ │ │ ├── VersionHistoryItemCollection.java │ │ │ │ ├── VersionItemCollection.java │ │ │ │ └── report/ │ │ │ │ ├── AbstractJcrReport.java │ │ │ │ ├── ExportViewReport.java │ │ │ │ ├── JcrPrivilegeReport.java │ │ │ │ ├── LocateByUuidReport.java │ │ │ │ ├── LocateCorrespondingNodeReport.java │ │ │ │ ├── NodeTypesReport.java │ │ │ │ ├── RegisteredNamespacesReport.java │ │ │ │ └── RepositoryDescriptorsReport.java │ │ │ └── simple/ │ │ │ ├── DavResourceImpl.java │ │ │ ├── DavSessionImpl.java │ │ │ ├── DavSessionProviderImpl.java │ │ │ ├── DefaultItemFilter.java │ │ │ ├── DeltaVResourceImpl.java │ │ │ ├── ItemFilter.java │ │ │ ├── LocatorFactoryImpl.java │ │ │ ├── LocatorFactoryImplEx.java │ │ │ ├── ResourceConfig.java │ │ │ ├── ResourceFactoryImpl.java │ │ │ ├── SimpleWebdavServlet.java │ │ │ ├── VersionControlledResourceImpl.java │ │ │ ├── VersionHistoryResourceImpl.java │ │ │ └── VersionResourceImpl.java │ │ ├── javadoc/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ ├── server/ │ │ │ │ └── io/ │ │ │ │ └── package.html │ │ │ └── webdav/ │ │ │ └── jcr/ │ │ │ ├── package.html │ │ │ └── version/ │ │ │ ├── package.html │ │ │ └── report/ │ │ │ └── package.html │ │ └── resources/ │ │ └── OSGI-INF/ │ │ └── l10n/ │ │ └── metatype.properties │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ ├── server/ │ │ │ ├── BasicCredentialsProviderTest.java │ │ │ ├── TestAll.java │ │ │ └── remoting/ │ │ │ └── davex/ │ │ │ ├── BatchReadConfigTest.java │ │ │ ├── DiffParserTest.java │ │ │ ├── JsonDiffHandlerImportTest.java │ │ │ ├── JsonDiffHandlerTest.java │ │ │ ├── JsonWriterTest.java │ │ │ └── TestAll.java │ │ └── webdav/ │ │ ├── jcr/ │ │ │ ├── JcrDavExceptionTest.java │ │ │ ├── JcrValueTypeTest.java │ │ │ ├── LockTimeOutFormatTest.java │ │ │ ├── LockTokenMappingTest.java │ │ │ ├── observation/ │ │ │ │ └── InfoMapTest.java │ │ │ └── security/ │ │ │ ├── AbstractSecurityTest.java │ │ │ ├── JcrSupportedPrivilegePropertyTest.java │ │ │ └── JcrUserPrivilegesPropertyTest.java │ │ ├── server/ │ │ │ ├── BindTest.java │ │ │ ├── ConditionalsTest.java │ │ │ ├── ContentCodingTest.java │ │ │ ├── HttpsSelfSignedTest.java │ │ │ ├── ProppatchTest.java │ │ │ ├── PutTest.java │ │ │ ├── RFC4918DestinationHeaderTest.java │ │ │ ├── RFC4918IfHeaderTest.java │ │ │ ├── RFC4918PropfindTest.java │ │ │ ├── RemotingTest.java │ │ │ └── WebDAVTestBase.java │ │ └── simple/ │ │ ├── LitmusTest.java │ │ ├── LocatorFactoryImplExTest.java │ │ └── ResourceConfigTest.java │ └── resources/ │ ├── config.xml │ ├── keystore │ ├── logback-test.xml │ ├── protectedHandlers.properties │ ├── protectedHandlersConfig.xml │ ├── repository.xml │ └── repositoryStubImpl.properties ├── jackrabbit-jcr-servlet/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── servlet/ │ ├── AbstractRepositoryServlet.java │ ├── ContextRepositoryServlet.java │ ├── FilterRepositoryFactory.java │ ├── JNDIBindingServlet.java │ ├── JNDIRepositoryServlet.java │ ├── ServletRepository.java │ ├── ServletRepositoryFactory.java │ ├── jackrabbit/ │ │ ├── JackrabbitRepositoryServlet.java │ │ └── StatisticsServlet.java │ └── login/ │ ├── AbstractLoginFilter.java │ ├── BasicLoginFilter.java │ ├── ContainerLoginFilter.java │ └── NullLoginFilter.java ├── jackrabbit-jcr-tests/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ └── main/ │ ├── appended-resources/ │ │ └── META-INF/ │ │ └── NOTICE │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── test/ │ │ ├── AbstractJCRTest.java │ │ ├── ConcurrentTestSuite.java │ │ ├── ISO8601.java │ │ ├── JCRTestResult.java │ │ ├── JCRTestSuite.java │ │ ├── JNDIRepositoryStub.java │ │ ├── JUnitTest.java │ │ ├── LogPrintWriter.java │ │ ├── NotExecutableException.java │ │ ├── RepositoryHelper.java │ │ ├── RepositoryHelperPool.java │ │ ├── RepositoryHelperPoolImpl.java │ │ ├── RepositoryStub.java │ │ ├── RepositoryStubException.java │ │ ├── TestAll.java │ │ ├── XMLChar.java │ │ └── api/ │ │ ├── AbstractImportXmlTest.java │ │ ├── AbstractPropertyTest.java │ │ ├── AbstractWorkspaceCopyBetweenTest.java │ │ ├── AbstractWorkspaceCopyTest.java │ │ ├── AbstractWorkspaceReferenceableTest.java │ │ ├── AbstractWorkspaceSameNameSibsTest.java │ │ ├── AbstractWorkspaceVersionableTest.java │ │ ├── AddNodeTest.java │ │ ├── Base64.java │ │ ├── BinaryPropertyTest.java │ │ ├── BooleanPropertyTest.java │ │ ├── CheckPermissionTest.java │ │ ├── DatePropertyTest.java │ │ ├── DocumentViewImportTest.java │ │ ├── DoublePropertyTest.java │ │ ├── EscapeJCRUtil.java │ │ ├── ExportDocViewTest.java │ │ ├── ExportSysViewTest.java │ │ ├── GetWeakReferencesTest.java │ │ ├── HasPermissionTest.java │ │ ├── ImpersonateTest.java │ │ ├── LifecycleTest.java │ │ ├── LongPropertyTest.java │ │ ├── NamePropertyTest.java │ │ ├── NameTest.java │ │ ├── NamespaceRegistryReadMethodsTest.java │ │ ├── NamespaceRegistryTest.java │ │ ├── NamespaceRemappingTest.java │ │ ├── NodeAddMixinTest.java │ │ ├── NodeCanAddMixinTest.java │ │ ├── NodeDiscoveringNodeTypesTest.java │ │ ├── NodeItemIsModifiedTest.java │ │ ├── NodeItemIsNewTest.java │ │ ├── NodeIteratorTest.java │ │ ├── NodeMixinUtil.java │ │ ├── NodeOrderableChildNodesTest.java │ │ ├── NodeReadMethodsTest.java │ │ ├── NodeRemoveMixinTest.java │ │ ├── NodeSetPrimaryTypeTest.java │ │ ├── NodeTest.java │ │ ├── NodeUUIDTest.java │ │ ├── PathPropertyTest.java │ │ ├── PathTest.java │ │ ├── PropertyItemIsModifiedTest.java │ │ ├── PropertyItemIsNewTest.java │ │ ├── PropertyReadMethodsTest.java │ │ ├── PropertyTest.java │ │ ├── PropertyTypeTest.java │ │ ├── PropertyUtil.java │ │ ├── ReferencePropertyTest.java │ │ ├── ReferenceableRootNodesTest.java │ │ ├── ReferencesTest.java │ │ ├── RepositoryDescriptorTest.java │ │ ├── RepositoryFactoryTest.java │ │ ├── RepositoryLoginTest.java │ │ ├── RootNodeTest.java │ │ ├── SerializationContext.java │ │ ├── SerializationTest.java │ │ ├── SessionReadMethodsTest.java │ │ ├── SessionRemoveItemTest.java │ │ ├── SessionTest.java │ │ ├── SessionUUIDTest.java │ │ ├── SetPropertyAssumeTypeTest.java │ │ ├── SetPropertyBooleanTest.java │ │ ├── SetPropertyCalendarTest.java │ │ ├── SetPropertyConstraintViolationExceptionTest.java │ │ ├── SetPropertyDecimalTest.java │ │ ├── SetPropertyDoubleTest.java │ │ ├── SetPropertyInputStreamTest.java │ │ ├── SetPropertyLongTest.java │ │ ├── SetPropertyNodeTest.java │ │ ├── SetPropertyStringTest.java │ │ ├── SetPropertyValueTest.java │ │ ├── SetValueBinaryTest.java │ │ ├── SetValueBooleanTest.java │ │ ├── SetValueConstraintViolationExceptionTest.java │ │ ├── SetValueDateTest.java │ │ ├── SetValueDecimalTest.java │ │ ├── SetValueDoubleTest.java │ │ ├── SetValueInputStreamTest.java │ │ ├── SetValueLongTest.java │ │ ├── SetValueReferenceTest.java │ │ ├── SetValueStringTest.java │ │ ├── SetValueValueFormatExceptionTest.java │ │ ├── SetValueVersionExceptionTest.java │ │ ├── ShareableNodeTest.java │ │ ├── StringPropertyTest.java │ │ ├── SysViewContentHandler.java │ │ ├── TestAll.java │ │ ├── TreeComparator.java │ │ ├── UndefinedPropertyTest.java │ │ ├── ValueFactoryTest.java │ │ ├── WorkspaceCloneReferenceableTest.java │ │ ├── WorkspaceCloneSameNameSibsTest.java │ │ ├── WorkspaceCloneTest.java │ │ ├── WorkspaceCloneVersionableTest.java │ │ ├── WorkspaceCopyBetweenWorkspacesReferenceableTest.java │ │ ├── WorkspaceCopyBetweenWorkspacesSameNameSibsTest.java │ │ ├── WorkspaceCopyBetweenWorkspacesTest.java │ │ ├── WorkspaceCopyBetweenWorkspacesVersionableTest.java │ │ ├── WorkspaceCopyReferenceableTest.java │ │ ├── WorkspaceCopySameNameSibsTest.java │ │ ├── WorkspaceCopyTest.java │ │ ├── WorkspaceCopyVersionableTest.java │ │ ├── WorkspaceManagementTest.java │ │ ├── WorkspaceMoveReferenceableTest.java │ │ ├── WorkspaceMoveSameNameSibsTest.java │ │ ├── WorkspaceMoveTest.java │ │ ├── WorkspaceMoveVersionableTest.java │ │ ├── WorkspaceReadMethodsTest.java │ │ ├── WorkspaceTest.java │ │ ├── lock/ │ │ │ ├── AbstractLockTest.java │ │ │ ├── DeepLockTest.java │ │ │ ├── LockManagerTest.java │ │ │ ├── LockTest.java │ │ │ ├── OpenScopedLockTest.java │ │ │ ├── SessionScopedLockTest.java │ │ │ ├── SetValueLockExceptionTest.java │ │ │ └── TestAll.java │ │ ├── nodetype/ │ │ │ ├── CanAddChildNodeCallWithNodeTypeTest.java │ │ │ ├── CanAddChildNodeCallWithoutNodeTypeTest.java │ │ │ ├── CanRemoveItemTest.java │ │ │ ├── CanSetPropertyBinaryTest.java │ │ │ ├── CanSetPropertyBooleanTest.java │ │ │ ├── CanSetPropertyDateTest.java │ │ │ ├── CanSetPropertyDoubleTest.java │ │ │ ├── CanSetPropertyLongTest.java │ │ │ ├── CanSetPropertyMultipleTest.java │ │ │ ├── CanSetPropertyNameTest.java │ │ │ ├── CanSetPropertyPathTest.java │ │ │ ├── CanSetPropertyStringTest.java │ │ │ ├── CanSetPropertyTest.java │ │ │ ├── NodeDefTest.java │ │ │ ├── NodeTypeCreationTest.java │ │ │ ├── NodeTypeManagerTest.java │ │ │ ├── NodeTypeTest.java │ │ │ ├── NodeTypeUtil.java │ │ │ ├── PredefinedNodeTypeTest.java │ │ │ ├── PropertyDefTest.java │ │ │ └── TestAll.java │ │ ├── observation/ │ │ │ ├── AbstractObservationTest.java │ │ │ ├── AddEventListenerTest.java │ │ │ ├── EventIteratorTest.java │ │ │ ├── EventJournalTest.java │ │ │ ├── EventResult.java │ │ │ ├── EventTest.java │ │ │ ├── GetDateTest.java │ │ │ ├── GetIdentifierTest.java │ │ │ ├── GetInfoTest.java │ │ │ ├── GetRegisteredEventListenersTest.java │ │ │ ├── GetUserDataTest.java │ │ │ ├── LockingTest.java │ │ │ ├── NodeAddedTest.java │ │ │ ├── NodeMovedTest.java │ │ │ ├── NodeRemovedTest.java │ │ │ ├── NodeReorderTest.java │ │ │ ├── PropertyAddedTest.java │ │ │ ├── PropertyChangedTest.java │ │ │ ├── PropertyRemovedTest.java │ │ │ ├── TestAll.java │ │ │ └── WorkspaceOperationTest.java │ │ ├── query/ │ │ │ ├── AbstractOrderByTest.java │ │ │ ├── AbstractQueryLevel2Test.java │ │ │ ├── AbstractQueryTest.java │ │ │ ├── CreateQueryTest.java │ │ │ ├── DerefQueryLevel1Test.java │ │ │ ├── ElementTest.java │ │ │ ├── GetLanguageTest.java │ │ │ ├── GetPersistentQueryPathLevel1Test.java │ │ │ ├── GetPersistentQueryPathTest.java │ │ │ ├── GetPropertyNamesTest.java │ │ │ ├── GetStatementTest.java │ │ │ ├── GetSupportedQueryLanguagesTest.java │ │ │ ├── OrderByDateTest.java │ │ │ ├── OrderByDecimalTest.java │ │ │ ├── OrderByDoubleTest.java │ │ │ ├── OrderByLengthTest.java │ │ │ ├── OrderByLocalNameTest.java │ │ │ ├── OrderByLongTest.java │ │ │ ├── OrderByLowerCaseTest.java │ │ │ ├── OrderByMultiTypeTest.java │ │ │ ├── OrderByNameTest.java │ │ │ ├── OrderByStringTest.java │ │ │ ├── OrderByURITest.java │ │ │ ├── OrderByUpperCaseTest.java │ │ │ ├── PredicatesTest.java │ │ │ ├── QueryResultNodeIteratorTest.java │ │ │ ├── SQLJcrPathTest.java │ │ │ ├── SQLJoinTest.java │ │ │ ├── SQLOrderByTest.java │ │ │ ├── SQLPathTest.java │ │ │ ├── SQLQueryLevel2Test.java │ │ │ ├── SaveTest.java │ │ │ ├── SetLimitTest.java │ │ │ ├── SetOffsetTest.java │ │ │ ├── SimpleSelectionTest.java │ │ │ ├── Statement.java │ │ │ ├── TestAll.java │ │ │ ├── TextNodeTest.java │ │ │ ├── XPathDocOrderTest.java │ │ │ ├── XPathJcrPathTest.java │ │ │ ├── XPathOrderByTest.java │ │ │ ├── XPathPosIndexTest.java │ │ │ ├── XPathQueryLevel2Test.java │ │ │ └── qom/ │ │ │ ├── AbstractJoinTest.java │ │ │ ├── AbstractQOMTest.java │ │ │ ├── AndConstraintTest.java │ │ │ ├── BindVariableValueTest.java │ │ │ ├── ChildNodeJoinConditionTest.java │ │ │ ├── ChildNodeTest.java │ │ │ ├── ColumnTest.java │ │ │ ├── DescendantNodeJoinConditionTest.java │ │ │ ├── DescendantNodeTest.java │ │ │ ├── EquiJoinConditionTest.java │ │ │ ├── FullTextSearchScoreTest.java │ │ │ ├── GetQueryTest.java │ │ │ ├── LengthTest.java │ │ │ ├── NodeLocalNameTest.java │ │ │ ├── NodeNameTest.java │ │ │ ├── NotConstraintTest.java │ │ │ ├── OrConstraintTest.java │ │ │ ├── OrderingTest.java │ │ │ ├── PropertyExistenceTest.java │ │ │ ├── PropertyValueTest.java │ │ │ ├── QueryObjectModelFactoryTest.java │ │ │ ├── RowTest.java │ │ │ ├── SameNodeJoinConditionTest.java │ │ │ ├── SameNodeTest.java │ │ │ ├── SelectorTest.java │ │ │ ├── TestAll.java │ │ │ └── UpperLowerCaseTest.java │ │ ├── retention/ │ │ │ ├── AbstractRetentionTest.java │ │ │ ├── HoldEffectTest.java │ │ │ ├── HoldTest.java │ │ │ ├── RetentionPolicyEffectTest.java │ │ │ ├── RetentionPolicyTest.java │ │ │ └── TestAll.java │ │ ├── security/ │ │ │ ├── AbstractAccessControlTest.java │ │ │ ├── AccessControlDiscoveryTest.java │ │ │ ├── AccessControlListTest.java │ │ │ ├── AccessControlPolicyIteratorTest.java │ │ │ ├── AccessControlPolicyTest.java │ │ │ ├── RSessionAccessControlDiscoveryTest.java │ │ │ ├── RSessionAccessControlPolicyTest.java │ │ │ ├── RSessionAccessControlTest.java │ │ │ └── TestAll.java │ │ ├── util/ │ │ │ ├── ISO9075.java │ │ │ ├── InputStreamWrapper.java │ │ │ ├── TestAll.java │ │ │ └── Text.java │ │ └── version/ │ │ ├── AbstractMergeTest.java │ │ ├── AbstractOnParentVersionTest.java │ │ ├── AbstractVersionTest.java │ │ ├── ActivitiesTest.java │ │ ├── CheckinTest.java │ │ ├── CheckoutTest.java │ │ ├── ConfigurationsTest.java │ │ ├── CopyTest.java │ │ ├── FrozenNodeTest.java │ │ ├── GetContainingHistoryTest.java │ │ ├── GetCreatedTest.java │ │ ├── GetPredecessorsTest.java │ │ ├── GetReferencesNodeTest.java │ │ ├── GetVersionableUUIDTest.java │ │ ├── MergeActivityTest.java │ │ ├── MergeCancelMergeTest.java │ │ ├── MergeCheckedoutSubNodeTest.java │ │ ├── MergeDoneMergeTest.java │ │ ├── MergeNodeIteratorTest.java │ │ ├── MergeNodeTest.java │ │ ├── MergeNonVersionableSubNodeTest.java │ │ ├── MergeShallowTest.java │ │ ├── MergeSubNodeTest.java │ │ ├── OnParentVersionAbortTest.java │ │ ├── OnParentVersionComputeTest.java │ │ ├── OnParentVersionCopyTest.java │ │ ├── OnParentVersionIgnoreTest.java │ │ ├── OnParentVersionInitializeTest.java │ │ ├── RemoveVersionTest.java │ │ ├── RestoreTest.java │ │ ├── SessionMoveVersionExceptionTest.java │ │ ├── TestAll.java │ │ ├── VersionGraphTest.java │ │ ├── VersionHistoryTest.java │ │ ├── VersionLabelTest.java │ │ ├── VersionStorageTest.java │ │ ├── VersionTest.java │ │ ├── WorkspaceMoveVersionExceptionTest.java │ │ ├── WorkspaceRestoreTest.java │ │ └── simple/ │ │ ├── AbstractVersionTest.java │ │ ├── BasicTest.java │ │ ├── CheckinTest.java │ │ ├── CheckoutTest.java │ │ ├── CopyTest.java │ │ ├── FrozenNodeTest.java │ │ ├── RestoreTest.java │ │ └── TestAll.java │ └── resources/ │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── test/ │ └── api/ │ └── nodetype/ │ └── spec/ │ ├── mix-created.txt │ ├── mix-etag.txt │ ├── mix-language.txt │ ├── mix-lastModified.txt │ ├── mix-lifecycle.txt │ ├── mix-lockable.txt │ ├── mix-mimeType.txt │ ├── mix-referenceable.txt │ ├── mix-shareable.txt │ ├── mix-simpleVersionable.txt │ ├── mix-title.txt │ ├── mix-versionable.txt │ ├── nt-activity.txt │ ├── nt-address.txt │ ├── nt-base.txt │ ├── nt-childNodeDefinition.txt │ ├── nt-configuration.txt │ ├── nt-file.txt │ ├── nt-folder.txt │ ├── nt-frozenNode.txt │ ├── nt-hierarchyNode.txt │ ├── nt-linkedFile.txt │ ├── nt-nodeType.txt │ ├── nt-propertyDefinition.txt │ ├── nt-query.txt │ ├── nt-resource.txt │ ├── nt-unstructured.txt │ ├── nt-version.txt │ ├── nt-versionHistory.txt │ ├── nt-versionLabels.txt │ └── nt-versionedChild.txt ├── jackrabbit-jcr2dav/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── jcr2dav/ │ │ │ └── Jcr2davRepositoryFactory.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── javax.jcr.RepositoryFactory │ │ └── accessControlProvider.properties │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── jcr2dav/ │ │ ├── ConformanceTest.java │ │ └── RepositoryStubImpl.java │ └── resources/ │ ├── logback-test.xml │ ├── protectedHandlersConfig.xml │ ├── repository.xml │ └── repositoryStubImpl.properties ├── jackrabbit-jcr2spi/ │ ├── README.txt │ ├── assembly.xml │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ └── NOTICE │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── jcr2spi/ │ │ │ ├── ItemCache.java │ │ │ ├── ItemCacheImpl.java │ │ │ ├── ItemImpl.java │ │ │ ├── ItemLifeCycleListener.java │ │ │ ├── ItemManager.java │ │ │ ├── ItemManagerImpl.java │ │ │ ├── Jcr2spiRepositoryFactory.java │ │ │ ├── JcrLockManager.java │ │ │ ├── JcrVersionManager.java │ │ │ ├── LazyItemIterator.java │ │ │ ├── ManagerProvider.java │ │ │ ├── NamespaceRegistryImpl.java │ │ │ ├── NamespaceStorage.java │ │ │ ├── NodeImpl.java │ │ │ ├── PropertyImpl.java │ │ │ ├── RepositoryImpl.java │ │ │ ├── SessionImpl.java │ │ │ ├── SessionListener.java │ │ │ ├── StaleProperty.java │ │ │ ├── WorkspaceImpl.java │ │ │ ├── WorkspaceManager.java │ │ │ ├── XASession.java │ │ │ ├── XASessionImpl.java │ │ │ ├── config/ │ │ │ │ ├── CacheBehaviour.java │ │ │ │ ├── RepositoryConfig.java │ │ │ │ └── package-info.java │ │ │ ├── hierarchy/ │ │ │ │ ├── ChildNodeAttic.java │ │ │ │ ├── ChildNodeEntries.java │ │ │ │ ├── ChildNodeEntriesImpl.java │ │ │ │ ├── ChildPropertyEntries.java │ │ │ │ ├── ChildPropertyEntriesImpl.java │ │ │ │ ├── EntryFactory.java │ │ │ │ ├── EntryValidation.java │ │ │ │ ├── HierarchyEntry.java │ │ │ │ ├── HierarchyEntryImpl.java │ │ │ │ ├── HierarchyEventListener.java │ │ │ │ ├── HierarchyManager.java │ │ │ │ ├── HierarchyManagerImpl.java │ │ │ │ ├── LinkedEntries.java │ │ │ │ ├── NodeEntry.java │ │ │ │ ├── NodeEntryImpl.java │ │ │ │ ├── PropertyEntry.java │ │ │ │ ├── PropertyEntryImpl.java │ │ │ │ └── UniqueIdResolver.java │ │ │ ├── lock/ │ │ │ │ ├── LockManagerImpl.java │ │ │ │ └── LockStateManager.java │ │ │ ├── nodetype/ │ │ │ │ ├── BitsetENTCacheImpl.java │ │ │ │ ├── DefinitionValidator.java │ │ │ │ ├── EffectiveNodeType.java │ │ │ │ ├── EffectiveNodeTypeCache.java │ │ │ │ ├── EffectiveNodeTypeImpl.java │ │ │ │ ├── EffectiveNodeTypeProvider.java │ │ │ │ ├── ItemDefinitionProvider.java │ │ │ │ ├── ItemDefinitionProviderImpl.java │ │ │ │ ├── NodeTypeCache.java │ │ │ │ ├── NodeTypeDefinitionProvider.java │ │ │ │ ├── NodeTypeImpl.java │ │ │ │ ├── NodeTypeManagerImpl.java │ │ │ │ ├── NodeTypeRegistry.java │ │ │ │ ├── NodeTypeRegistryImpl.java │ │ │ │ └── NodeTypeRegistryListener.java │ │ │ ├── observation/ │ │ │ │ ├── EventImpl.java │ │ │ │ ├── EventJournalImpl.java │ │ │ │ ├── FilteredEventIterator.java │ │ │ │ ├── InternalEventListener.java │ │ │ │ └── ObservationManagerImpl.java │ │ │ ├── operation/ │ │ │ │ ├── AbstractCopy.java │ │ │ │ ├── AbstractOperation.java │ │ │ │ ├── AbstractRemove.java │ │ │ │ ├── AddLabel.java │ │ │ │ ├── AddNode.java │ │ │ │ ├── AddProperty.java │ │ │ │ ├── Checkin.java │ │ │ │ ├── Checkout.java │ │ │ │ ├── Checkpoint.java │ │ │ │ ├── Clone.java │ │ │ │ ├── Copy.java │ │ │ │ ├── CreateActivity.java │ │ │ │ ├── CreateConfiguration.java │ │ │ │ ├── IgnoreOperation.java │ │ │ │ ├── LockOperation.java │ │ │ │ ├── LockRefresh.java │ │ │ │ ├── LockRelease.java │ │ │ │ ├── Merge.java │ │ │ │ ├── Move.java │ │ │ │ ├── Operation.java │ │ │ │ ├── OperationVisitor.java │ │ │ │ ├── Remove.java │ │ │ │ ├── RemoveActivity.java │ │ │ │ ├── RemoveLabel.java │ │ │ │ ├── RemoveVersion.java │ │ │ │ ├── ReorderNodes.java │ │ │ │ ├── ResolveMergeConflict.java │ │ │ │ ├── Restore.java │ │ │ │ ├── SetMixin.java │ │ │ │ ├── SetPrimaryType.java │ │ │ │ ├── SetPropertyValue.java │ │ │ │ ├── SetTree.java │ │ │ │ ├── TransientOperation.java │ │ │ │ ├── TransientOperationVisitor.java │ │ │ │ ├── Update.java │ │ │ │ └── WorkspaceImport.java │ │ │ ├── package-info.java │ │ │ ├── query/ │ │ │ │ ├── NodeIteratorImpl.java │ │ │ │ ├── QueryImpl.java │ │ │ │ ├── QueryManagerImpl.java │ │ │ │ ├── QueryObjectModelImpl.java │ │ │ │ ├── QueryResultImpl.java │ │ │ │ ├── RowIteratorImpl.java │ │ │ │ └── ScoreNodeIterator.java │ │ │ ├── security/ │ │ │ │ ├── AccessManager.java │ │ │ │ ├── SecurityConstants.java │ │ │ │ └── authorization/ │ │ │ │ ├── AccessControlProvider.java │ │ │ │ ├── AccessControlProviderStub.java │ │ │ │ ├── PrivilegeImpl.java │ │ │ │ └── jackrabbit/ │ │ │ │ ├── AccessControlConstants.java │ │ │ │ └── acl/ │ │ │ │ ├── AccessControlEntryImpl.java │ │ │ │ ├── AccessControlListImpl.java │ │ │ │ ├── AccessControlManagerImpl.java │ │ │ │ └── AccessControlProviderImpl.java │ │ │ ├── state/ │ │ │ │ ├── AbstractItemStateFactory.java │ │ │ │ ├── ChangeLog.java │ │ │ │ ├── ItemState.java │ │ │ │ ├── ItemStateCreationListener.java │ │ │ │ ├── ItemStateFactory.java │ │ │ │ ├── ItemStateLifeCycleListener.java │ │ │ │ ├── ItemStateValidator.java │ │ │ │ ├── NodeState.java │ │ │ │ ├── PropertyState.java │ │ │ │ ├── SessionItemStateManager.java │ │ │ │ ├── Status.java │ │ │ │ ├── TransientISFactory.java │ │ │ │ ├── TransientItemStateFactory.java │ │ │ │ ├── TransientItemStateManager.java │ │ │ │ ├── UpdatableItemStateManager.java │ │ │ │ └── WorkspaceItemStateFactory.java │ │ │ ├── util/ │ │ │ │ ├── LogUtil.java │ │ │ │ ├── ReferenceChangeTracker.java │ │ │ │ └── StateUtility.java │ │ │ ├── version/ │ │ │ │ ├── VersionHistoryImpl.java │ │ │ │ ├── VersionImpl.java │ │ │ │ ├── VersionManager.java │ │ │ │ └── VersionManagerImpl.java │ │ │ └── xml/ │ │ │ ├── DocViewImportHandler.java │ │ │ ├── ImportHandler.java │ │ │ ├── Importer.java │ │ │ ├── SessionImporter.java │ │ │ ├── SysViewImportHandler.java │ │ │ ├── TargetImportHandler.java │ │ │ └── WorkspaceContentHandler.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── javax.jcr.RepositoryFactory │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── jcr2spi/ │ │ ├── AbstractJCR2SPITest.java │ │ ├── AbstractMoveTest.java │ │ ├── AbstractMoveTreeTest.java │ │ ├── AbstractRepositoryConfig.java │ │ ├── AccessByRelativePathTest.java │ │ ├── AddNewPropertyTest.java │ │ ├── AddNodeTest.java │ │ ├── AddPropertyTest.java │ │ ├── BinaryTest.java │ │ ├── CopyMoveToJsonTest.java │ │ ├── ExternalModificationTest.java │ │ ├── GetItemsTest.java │ │ ├── GetPropertyTest.java │ │ ├── HierarchyNodeTest.java │ │ ├── IsSameTest.java │ │ ├── ItemInfoStore.java │ │ ├── Jcr2SpiTestSuite.java │ │ ├── LazyItemIteratorTest.java │ │ ├── LoginTest.java │ │ ├── MixinModificationTest.java │ │ ├── MoveCombinedTest.java │ │ ├── MoveMultipleTest.java │ │ ├── MoveNewTreeTest.java │ │ ├── MoveReferenceableTest.java │ │ ├── MoveSNSTest.java │ │ ├── MoveTest.java │ │ ├── MoveToNewTest.java │ │ ├── MoveTreeTest.java │ │ ├── MultiValuedPropertyTest.java │ │ ├── NodeOrderTest.java │ │ ├── PropertyLengthTest.java │ │ ├── RefreshFalseTest.java │ │ ├── RefreshMovedTest.java │ │ ├── RefreshTrueTest.java │ │ ├── RemoveItemTest.java │ │ ├── RemoveMovedNodeTest.java │ │ ├── RemoveNewNodeTest.java │ │ ├── RemoveNodeTest.java │ │ ├── RemovePropertyTest.java │ │ ├── RemoveReferenceableNodeTest.java │ │ ├── RemoveSNSTest.java │ │ ├── RenameTest.java │ │ ├── ReorderMixedTest.java │ │ ├── ReorderMoveTest.java │ │ ├── ReorderNewAndSavedTest.java │ │ ├── ReorderNewSNSTest.java │ │ ├── ReorderNewTest.java │ │ ├── ReorderReferenceableSNSTest.java │ │ ├── ReorderSNSTest.java │ │ ├── ReorderTest.java │ │ ├── ReplaceNodeTest.java │ │ ├── RevertMoveTest.java │ │ ├── SNSIndexTest.java │ │ ├── SingleValuedPropertyTest.java │ │ ├── TestAll.java │ │ ├── TestConnect.java │ │ ├── UpdateTest.java │ │ ├── WorkspaceMoveTest.java │ │ ├── WorkspaceTest.java │ │ ├── benchmark/ │ │ │ └── ReadPerformanceTest.java │ │ ├── hierarchy/ │ │ │ └── LinkedEntriesTest.java │ │ ├── lock/ │ │ │ ├── AbstractLockTest.java │ │ │ ├── DeepLockTest.java │ │ │ ├── OpenScopedLockTest.java │ │ │ ├── SessionScopedLockTest.java │ │ │ └── TestAll.java │ │ ├── name/ │ │ │ ├── NamespaceRegistryTest.java │ │ │ └── TestAll.java │ │ ├── nodetype/ │ │ │ ├── AddMixinTest.java │ │ │ ├── MandatoryItemTest.java │ │ │ ├── NodeTypeImplTest.java │ │ │ ├── NodeTypeManagerImplTest.java │ │ │ ├── RemoveMixinTest.java │ │ │ └── TestAll.java │ │ ├── observation/ │ │ │ ├── ObservationTest.java │ │ │ └── TestAll.java │ │ ├── query/ │ │ │ ├── QueryTest.java │ │ │ └── TestAll.java │ │ ├── security/ │ │ │ ├── Jcr2SpiSecurityTestSuite.java │ │ │ └── authorization/ │ │ │ └── jackrabbit/ │ │ │ └── acl/ │ │ │ ├── AccessControlListImplTest.java │ │ │ ├── AccessControlManagerImplTest.java │ │ │ └── TestAll.java │ │ ├── version/ │ │ │ ├── LabelTest.java │ │ │ └── TestAll.java │ │ └── xml/ │ │ ├── SessionImportTest.java │ │ └── TestAll.java │ └── resources/ │ ├── accessControlProvider.properties │ ├── logback-test.xml │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── jcr2spi/ │ └── default-nodetypes.cnd ├── jackrabbit-parent/ │ └── pom.xml ├── jackrabbit-spi/ │ ├── README.txt │ ├── assembly.xml │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ └── NOTICE │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── spi/ │ │ ├── Batch.java │ │ ├── ChildInfo.java │ │ ├── Event.java │ │ ├── EventBundle.java │ │ ├── EventFilter.java │ │ ├── IdFactory.java │ │ ├── ItemId.java │ │ ├── ItemInfo.java │ │ ├── ItemInfoCache.java │ │ ├── LockInfo.java │ │ ├── Name.java │ │ ├── NameFactory.java │ │ ├── NodeId.java │ │ ├── NodeInfo.java │ │ ├── Path.java │ │ ├── PathFactory.java │ │ ├── PrivilegeDefinition.java │ │ ├── PropertyId.java │ │ ├── PropertyInfo.java │ │ ├── QItemDefinition.java │ │ ├── QNodeDefinition.java │ │ ├── QNodeTypeDefinition.java │ │ ├── QPropertyDefinition.java │ │ ├── QValue.java │ │ ├── QValueConstraint.java │ │ ├── QValueFactory.java │ │ ├── QueryInfo.java │ │ ├── QueryResultRow.java │ │ ├── RepositoryService.java │ │ ├── RepositoryServiceFactory.java │ │ ├── SessionInfo.java │ │ ├── Subscription.java │ │ ├── Tree.java │ │ ├── XASessionInfo.java │ │ └── package-info.java │ └── test/ │ └── java/ │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── spi/ │ ├── AbstractSPITest.java │ ├── Helper.java │ ├── QValueFactoryTest.java │ ├── QValueTest.java │ ├── RepositoryServiceStub.java │ ├── RepositoryServiceTest.java │ ├── SessionInfoTest.java │ └── TestAll.java ├── jackrabbit-spi-commons/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ ├── LICENSE │ │ │ └── NOTICE │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── spi/ │ │ │ └── commons/ │ │ │ ├── AbstractReadableRepositoryService.java │ │ │ ├── AbstractRepositoryService.java │ │ │ ├── AdditionalEventInfo.java │ │ │ ├── ChildInfoImpl.java │ │ │ ├── EventBundleImpl.java │ │ │ ├── EventFilterImpl.java │ │ │ ├── EventImpl.java │ │ │ ├── ItemInfoBuilder.java │ │ │ ├── ItemInfoCacheImpl.java │ │ │ ├── ItemInfoImpl.java │ │ │ ├── LockInfoImpl.java │ │ │ ├── NodeInfoImpl.java │ │ │ ├── PropertyInfoImpl.java │ │ │ ├── QItemDefinitionImpl.java │ │ │ ├── QNodeDefinitionImpl.java │ │ │ ├── QNodeTypeDefinitionImpl.java │ │ │ ├── QPropertyDefinitionImpl.java │ │ │ ├── SerializableBatch.java │ │ │ ├── SessionExtensions.java │ │ │ ├── SessionInfoImpl.java │ │ │ ├── batch/ │ │ │ │ ├── AbstractChangeLog.java │ │ │ │ ├── ChangeLog.java │ │ │ │ ├── ChangeLogImpl.java │ │ │ │ ├── ConsolidatingChangeLog.java │ │ │ │ ├── Operation.java │ │ │ │ ├── Operations.java │ │ │ │ └── package-info.java │ │ │ ├── conversion/ │ │ │ │ ├── CachingNameResolver.java │ │ │ │ ├── CachingPathResolver.java │ │ │ │ ├── DefaultNamePathResolver.java │ │ │ │ ├── GenerationalCache.java │ │ │ │ ├── IdentifierResolver.java │ │ │ │ ├── IllegalNameException.java │ │ │ │ ├── MalformedPathException.java │ │ │ │ ├── NameException.java │ │ │ │ ├── NameParser.java │ │ │ │ ├── NamePathResolver.java │ │ │ │ ├── NameResolver.java │ │ │ │ ├── ParsingNameResolver.java │ │ │ │ ├── ParsingPathResolver.java │ │ │ │ ├── PathParser.java │ │ │ │ ├── PathResolver.java │ │ │ │ └── package-info.java │ │ │ ├── identifier/ │ │ │ │ ├── AbstractIdFactory.java │ │ │ │ ├── IdFactoryImpl.java │ │ │ │ └── package-info.java │ │ │ ├── iterator/ │ │ │ │ ├── BoundedIterator.java │ │ │ │ ├── Iterators.java │ │ │ │ ├── Predicate.java │ │ │ │ ├── Predicates.java │ │ │ │ ├── Transformer.java │ │ │ │ └── package-info.java │ │ │ ├── lock/ │ │ │ │ ├── Locked.java │ │ │ │ └── package-info.java │ │ │ ├── logging/ │ │ │ │ ├── AbstractLogger.java │ │ │ │ ├── BatchLogger.java │ │ │ │ ├── IdFactoryLogger.java │ │ │ │ ├── LogWriter.java │ │ │ │ ├── LogWriterProvider.java │ │ │ │ ├── NameFactoryLogger.java │ │ │ │ ├── PathFactoryLogger.java │ │ │ │ ├── QValueFactoryLogger.java │ │ │ │ ├── RepositoryServiceLogger.java │ │ │ │ ├── SessionInfoLogger.java │ │ │ │ ├── Slf4jLogWriter.java │ │ │ │ ├── Slf4jLogWriterProvider.java │ │ │ │ ├── SpiLoggerFactory.java │ │ │ │ ├── WriterLogWriter.java │ │ │ │ ├── WriterLogWriterProvider.java │ │ │ │ └── package-info.java │ │ │ ├── name/ │ │ │ │ ├── AbstractPath.java │ │ │ │ ├── CurrentPath.java │ │ │ │ ├── HashCache.java │ │ │ │ ├── IdentifierPath.java │ │ │ │ ├── MatchResult.java │ │ │ │ ├── Matcher.java │ │ │ │ ├── NameConstants.java │ │ │ │ ├── NameFactoryImpl.java │ │ │ │ ├── NamePath.java │ │ │ │ ├── ParentPath.java │ │ │ │ ├── PathBuilder.java │ │ │ │ ├── PathFactoryImpl.java │ │ │ │ ├── PathMap.java │ │ │ │ ├── Pattern.java │ │ │ │ ├── RelativePath.java │ │ │ │ ├── RootPath.java │ │ │ │ └── package-info.java │ │ │ ├── namespace/ │ │ │ │ ├── AbstractNamespaceResolver.java │ │ │ │ ├── NamespaceAdder.java │ │ │ │ ├── NamespaceExtractor.java │ │ │ │ ├── NamespaceListener.java │ │ │ │ ├── NamespaceMapping.java │ │ │ │ ├── NamespaceResolver.java │ │ │ │ ├── RegistryNamespaceResolver.java │ │ │ │ ├── SessionNamespaceResolver.java │ │ │ │ └── package-info.java │ │ │ ├── nodetype/ │ │ │ │ ├── AbstractItemDefinitionTemplate.java │ │ │ │ ├── AbstractNodeType.java │ │ │ │ ├── AbstractNodeTypeManager.java │ │ │ │ ├── InvalidConstraintException.java │ │ │ │ ├── ItemDefinitionImpl.java │ │ │ │ ├── NodeDefinitionImpl.java │ │ │ │ ├── NodeDefinitionTemplateImpl.java │ │ │ │ ├── NodeTypeConflictException.java │ │ │ │ ├── NodeTypeDefDiff.java │ │ │ │ ├── NodeTypeDefinitionFactory.java │ │ │ │ ├── NodeTypeDefinitionImpl.java │ │ │ │ ├── NodeTypeStorage.java │ │ │ │ ├── NodeTypeStorageImpl.java │ │ │ │ ├── NodeTypeTemplateImpl.java │ │ │ │ ├── PropertyDefinitionImpl.java │ │ │ │ ├── PropertyDefinitionTemplateImpl.java │ │ │ │ ├── QDefinitionBuilderFactory.java │ │ │ │ ├── QItemDefinitionBuilder.java │ │ │ │ ├── QNodeDefinitionBuilder.java │ │ │ │ ├── QNodeTypeDefinitionBuilder.java │ │ │ │ ├── QPropertyDefinitionBuilder.java │ │ │ │ ├── compact/ │ │ │ │ │ ├── CompactNodeTypeDefWriter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── constraint/ │ │ │ │ │ ├── BooleanConstraint.java │ │ │ │ │ ├── DateConstraint.java │ │ │ │ │ ├── NameConstraint.java │ │ │ │ │ ├── NumericConstraint.java │ │ │ │ │ ├── PathConstraint.java │ │ │ │ │ ├── ReferenceConstraint.java │ │ │ │ │ ├── StringConstraint.java │ │ │ │ │ ├── ValueConstraint.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── privilege/ │ │ │ │ ├── ParseException.java │ │ │ │ ├── PrivilegeDefinitionImpl.java │ │ │ │ ├── PrivilegeDefinitionReader.java │ │ │ │ ├── PrivilegeDefinitionWriter.java │ │ │ │ ├── PrivilegeHandler.java │ │ │ │ ├── PrivilegeXmlHandler.java │ │ │ │ └── package-info.java │ │ │ ├── query/ │ │ │ │ ├── AndQueryNode.java │ │ │ │ ├── ConstantNameProvider.java │ │ │ │ ├── DefaultQueryNodeFactory.java │ │ │ │ ├── DefaultQueryNodeVisitor.java │ │ │ │ ├── DerefQueryNode.java │ │ │ │ ├── ExactQueryNode.java │ │ │ │ ├── LocationStepQueryNode.java │ │ │ │ ├── NAryQueryNode.java │ │ │ │ ├── NodeTypeQueryNode.java │ │ │ │ ├── NotQueryNode.java │ │ │ │ ├── OrQueryNode.java │ │ │ │ ├── OrderQueryNode.java │ │ │ │ ├── PathQueryNode.java │ │ │ │ ├── PropertyFunctionQueryNode.java │ │ │ │ ├── QueryConstants.java │ │ │ │ ├── QueryNode.java │ │ │ │ ├── QueryNodeFactory.java │ │ │ │ ├── QueryNodeVisitor.java │ │ │ │ ├── QueryParser.java │ │ │ │ ├── QueryRootNode.java │ │ │ │ ├── QueryTreeBuilder.java │ │ │ │ ├── QueryTreeBuilderRegistry.java │ │ │ │ ├── QueryTreeDump.java │ │ │ │ ├── RelationQueryNode.java │ │ │ │ ├── TextsearchQueryNode.java │ │ │ │ ├── TraversingQueryNodeVisitor.java │ │ │ │ ├── package-info.java │ │ │ │ ├── qom/ │ │ │ │ │ ├── AbstractQOMNode.java │ │ │ │ │ ├── AndImpl.java │ │ │ │ │ ├── BindVariableValueImpl.java │ │ │ │ │ ├── ChildNodeImpl.java │ │ │ │ │ ├── ChildNodeJoinConditionImpl.java │ │ │ │ │ ├── ColumnImpl.java │ │ │ │ │ ├── ComparisonImpl.java │ │ │ │ │ ├── ConstraintImpl.java │ │ │ │ │ ├── DefaultQOMTreeVisitor.java │ │ │ │ │ ├── DefaultTraversingQOMTreeVisitor.java │ │ │ │ │ ├── DescendantNodeImpl.java │ │ │ │ │ ├── DescendantNodeJoinConditionImpl.java │ │ │ │ │ ├── DynamicOperandImpl.java │ │ │ │ │ ├── EquiJoinConditionImpl.java │ │ │ │ │ ├── FullTextSearchImpl.java │ │ │ │ │ ├── FullTextSearchScoreImpl.java │ │ │ │ │ ├── JoinConditionImpl.java │ │ │ │ │ ├── JoinImpl.java │ │ │ │ │ ├── LengthImpl.java │ │ │ │ │ ├── LiteralImpl.java │ │ │ │ │ ├── LowerCaseImpl.java │ │ │ │ │ ├── NodeLocalNameImpl.java │ │ │ │ │ ├── NodeNameImpl.java │ │ │ │ │ ├── NotImpl.java │ │ │ │ │ ├── OrImpl.java │ │ │ │ │ ├── OrderingImpl.java │ │ │ │ │ ├── PropertyExistenceImpl.java │ │ │ │ │ ├── PropertyValueImpl.java │ │ │ │ │ ├── QOMTreeVisitor.java │ │ │ │ │ ├── QueryObjectModelFactoryImpl.java │ │ │ │ │ ├── QueryObjectModelTree.java │ │ │ │ │ ├── SameNodeImpl.java │ │ │ │ │ ├── SameNodeJoinConditionImpl.java │ │ │ │ │ ├── SelectorImpl.java │ │ │ │ │ ├── SourceImpl.java │ │ │ │ │ ├── StaticOperandImpl.java │ │ │ │ │ ├── UpperCaseImpl.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── sql/ │ │ │ │ │ ├── ASTAndExpression.java │ │ │ │ │ ├── ASTAscendingOrderSpec.java │ │ │ │ │ ├── ASTBracketExpression.java │ │ │ │ │ ├── ASTContainsExpression.java │ │ │ │ │ ├── ASTDescendingOrderSpec.java │ │ │ │ │ ├── ASTExcerptFunction.java │ │ │ │ │ ├── ASTFromClause.java │ │ │ │ │ ├── ASTIdentifier.java │ │ │ │ │ ├── ASTLiteral.java │ │ │ │ │ ├── ASTLowerFunction.java │ │ │ │ │ ├── ASTNotExpression.java │ │ │ │ │ ├── ASTOrExpression.java │ │ │ │ │ ├── ASTOrderByClause.java │ │ │ │ │ ├── ASTOrderSpec.java │ │ │ │ │ ├── ASTPredicate.java │ │ │ │ │ ├── ASTQuery.java │ │ │ │ │ ├── ASTSelectList.java │ │ │ │ │ ├── ASTUpperFunction.java │ │ │ │ │ ├── ASTWhereClause.java │ │ │ │ │ ├── DefaultParserVisitor.java │ │ │ │ │ ├── JCRSQLParser.java │ │ │ │ │ ├── JCRSQLParserConstants.java │ │ │ │ │ ├── JCRSQLParserTokenManager.java │ │ │ │ │ ├── JCRSQLParserTreeConstants.java │ │ │ │ │ ├── JCRSQLParserVisitor.java │ │ │ │ │ ├── JCRSQLQueryBuilder.java │ │ │ │ │ ├── JJTJCRSQLParserState.java │ │ │ │ │ ├── Node.java │ │ │ │ │ ├── ParseException.java │ │ │ │ │ ├── QueryBuilder.java │ │ │ │ │ ├── QueryFormat.java │ │ │ │ │ ├── SimpleCharStream.java │ │ │ │ │ ├── SimpleNode.java │ │ │ │ │ ├── Token.java │ │ │ │ │ ├── TokenMgrError.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── sql2/ │ │ │ │ │ ├── Parser.java │ │ │ │ │ └── package-info.java │ │ │ │ └── xpath/ │ │ │ │ ├── JJTXPathState.java │ │ │ │ ├── Node.java │ │ │ │ ├── ParseException.java │ │ │ │ ├── QueryBuilder.java │ │ │ │ ├── QueryFormat.java │ │ │ │ ├── SimpleCharStream.java │ │ │ │ ├── SimpleNode.java │ │ │ │ ├── Token.java │ │ │ │ ├── TokenMgrError.java │ │ │ │ ├── XPath.java │ │ │ │ ├── XPathConstants.java │ │ │ │ ├── XPathQueryBuilder.java │ │ │ │ ├── XPathTokenManager.java │ │ │ │ ├── XPathTreeConstants.java │ │ │ │ ├── XPathVisitor.java │ │ │ │ └── package-info.java │ │ │ ├── tree/ │ │ │ │ ├── AbstractTree.java │ │ │ │ └── package-info.java │ │ │ ├── util/ │ │ │ │ ├── StringCache.java │ │ │ │ └── package-info.java │ │ │ └── value/ │ │ │ ├── AbstractQValue.java │ │ │ ├── AbstractQValueFactory.java │ │ │ ├── DefaultQValue.java │ │ │ ├── QValueFactoryImpl.java │ │ │ ├── QValueValue.java │ │ │ ├── ValueFactoryQImpl.java │ │ │ ├── ValueFormat.java │ │ │ └── package-info.java │ │ ├── javacc/ │ │ │ ├── sql/ │ │ │ │ └── JCRSQL.jjt │ │ │ └── xpath/ │ │ │ ├── XPath.jjt │ │ │ ├── javacc.xsl │ │ │ ├── jjtree-jackrabbit.xsl │ │ │ ├── jjtree.xsl │ │ │ ├── strip.xsl │ │ │ └── xpath-grammar.xml │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.apache.jackrabbit.spi.commons.query.QueryTreeBuilder │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── spi/ │ │ └── commons/ │ │ ├── batch/ │ │ │ └── ConsolidatedBatchTest.java │ │ ├── conversion/ │ │ │ ├── DummyIdentifierResolver.java │ │ │ ├── DummyNamespaceResolver.java │ │ │ ├── NameParserTest.java │ │ │ ├── ParsingNameResolverTest.java │ │ │ ├── ParsingPathResolverTest.java │ │ │ └── PathParserTest.java │ │ ├── identifier/ │ │ │ └── SerializationTest.java │ │ ├── name/ │ │ │ ├── ElementTest.java │ │ │ ├── JcrName.java │ │ │ ├── JcrPath.java │ │ │ ├── MatcherTest.java │ │ │ ├── NameFactoryTest.java │ │ │ ├── PathBuilderTest.java │ │ │ ├── PathFactoryTest.java │ │ │ ├── PathTest.java │ │ │ └── PatternTest.java │ │ ├── nodetype/ │ │ │ ├── NodeDefinitionTemplateImplTest.java │ │ │ ├── NodeTypeDefDiffTest.java │ │ │ ├── PropertyDefinitionTemplateImplTest.java │ │ │ ├── TestAll.java │ │ │ ├── compact/ │ │ │ │ └── CompactNodeTypeDefTest.java │ │ │ └── constraint/ │ │ │ ├── BooleanConstraintTest.java │ │ │ ├── DateConstraintTest.java │ │ │ ├── NameConstraintTest.java │ │ │ ├── NumericConstraintTest.java │ │ │ ├── PathConstraintTest.java │ │ │ ├── ReferenceConstraintTest.java │ │ │ ├── StringConstraintTest.java │ │ │ └── ValueConstraintTest.java │ │ ├── privilege/ │ │ │ ├── PrivilegeHandlerTest.java │ │ │ └── TestAll.java │ │ ├── query/ │ │ │ ├── sql2/ │ │ │ │ └── ParserTest.java │ │ │ └── xpath/ │ │ │ ├── QueryFormatTest.java │ │ │ └── XPathOrderByTest.java │ │ └── value/ │ │ ├── QValueTest.java │ │ └── ValueFormatTest.java │ └── resources/ │ ├── cnd-reader-test-input.cnd │ ├── logback-test.xml │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── spi/ │ └── commons/ │ ├── privilege/ │ │ ├── readtest.xml │ │ └── writetest.xml │ └── query/ │ └── sql2/ │ └── test.sql2.txt ├── jackrabbit-spi2dav/ │ ├── README.txt │ ├── assembly.xml │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ ├── spi2dav/ │ │ │ ├── BatchUtils.java │ │ │ ├── ConnectionOptions.java │ │ │ ├── CredentialsWrapper.java │ │ │ ├── DefinitionUtil.java │ │ │ ├── DocumentTree.java │ │ │ ├── EventImpl.java │ │ │ ├── EventSubscriptionImpl.java │ │ │ ├── ExceptionConverter.java │ │ │ ├── IdURICache.java │ │ │ ├── ItemInfoImpl.java │ │ │ ├── ItemResourceConstants.java │ │ │ ├── LockInfoImpl.java │ │ │ ├── NamespaceResolverImpl.java │ │ │ ├── NodeInfoImpl.java │ │ │ ├── PropertyInfoImpl.java │ │ │ ├── QueryInfoImpl.java │ │ │ ├── QueryResultRowImpl.java │ │ │ ├── RepositoryServiceImpl.java │ │ │ ├── SessionInfoImpl.java │ │ │ ├── Spi2davRepositoryServiceFactory.java │ │ │ ├── URIResolver.java │ │ │ └── URIResolverImpl.java │ │ └── spi2davex/ │ │ ├── BatchReadConfig.java │ │ ├── ChildInfoImpl.java │ │ ├── HttpPost.java │ │ ├── ItemInfoImpl.java │ │ ├── ItemInfoJSONHandler.java │ │ ├── NodeInfoImpl.java │ │ ├── PropertyInfoImpl.java │ │ ├── QValueFactoryImpl.java │ │ ├── RepositoryServiceImpl.java │ │ ├── Spi2davexRepositoryServiceFactory.java │ │ ├── Utils.java │ │ └── ValueLoader.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ ├── spi2dav/ │ │ │ ├── ConnectionOptionsTest.java │ │ │ ├── ConnectionTest.java │ │ │ ├── DavPropertyTest.java │ │ │ ├── IdURICacheTest.java │ │ │ ├── RepositoryServiceImplIT.java │ │ │ ├── RepositoryServiceImplTest.java │ │ │ ├── RepositoryStubImpl.java │ │ │ ├── ServiceStubImpl.java │ │ │ └── TestAll.java │ │ ├── spi2davex/ │ │ │ ├── BatchTest.java │ │ │ ├── CloneTest.java │ │ │ ├── ConnectionTest.java │ │ │ ├── CopyTest.java │ │ │ ├── CreateFileTest.java │ │ │ ├── ExtensionTest.java │ │ │ ├── ReadTest.java │ │ │ ├── RepositoryStubImpl.java │ │ │ ├── ServiceStubImpl.java │ │ │ └── TestAll.java │ │ └── test/ │ │ └── TestAll.java │ └── resources/ │ ├── config.xml │ ├── disabledrepositoryServiceStubImpl.properties │ ├── disabledrepositoryStubImpl.properties │ ├── log4j.properties │ ├── logback-test.xml │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── spi2dav/ │ └── emptyPKCS12.keystore ├── jackrabbit-spi2jcr/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ └── NOTICE │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── spi2jcr/ │ │ ├── BatchReadConfig.java │ │ ├── ChildInfoImpl.java │ │ ├── EventFactory.java │ │ ├── EventSubscription.java │ │ ├── IdFactoryImpl.java │ │ ├── LockInfoImpl.java │ │ ├── NodeInfoImpl.java │ │ ├── PropertyInfoImpl.java │ │ ├── QueryInfoImpl.java │ │ ├── QueryResultRowImpl.java │ │ ├── RepositoryServiceImpl.java │ │ ├── SessionInfoImpl.java │ │ ├── Spi2jcrRepositoryServiceFactory.java │ │ └── XmlTree.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ ├── spi2jcr/ │ │ │ ├── RepositoryStubImpl.java │ │ │ ├── ServiceStubImpl.java │ │ │ ├── jcr2spi/ │ │ │ │ └── TestAll.java │ │ │ └── spi/ │ │ │ └── TestAll.java │ │ └── test/ │ │ ├── JCRBenchmark.java │ │ └── TestAll.java │ └── resources/ │ ├── logback-test.xml │ ├── repository.xml │ ├── repositoryServiceStubImpl.properties │ └── repositoryStubImpl.properties ├── jackrabbit-standalone/ │ └── pom.xml ├── jackrabbit-standalone-components/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── appended-resources/ │ │ └── META-INF/ │ │ ├── LICENSE │ │ └── NOTICE │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── standalone/ │ │ ├── Main.java │ │ ├── cli/ │ │ │ ├── AbstractParameter.java │ │ │ ├── Argument.java │ │ │ ├── CommandException.java │ │ │ ├── CommandHelper.java │ │ │ ├── CommandLine.java │ │ │ ├── CommandLineFactory.java │ │ │ ├── ConfigurationException.java │ │ │ ├── Flag.java │ │ │ ├── JcrClient.java │ │ │ ├── JcrParser.java │ │ │ ├── JcrParserException.java │ │ │ ├── Option.java │ │ │ ├── SourceCommand.java │ │ │ ├── collect/ │ │ │ │ ├── AbstractCollect.java │ │ │ │ ├── CollectItems.java │ │ │ │ ├── CollectNodes.java │ │ │ │ └── CollectProperties.java │ │ │ ├── core/ │ │ │ │ ├── AbstractSetProperty.java │ │ │ │ ├── AddNode.java │ │ │ │ ├── ClearWorkspace.java │ │ │ │ ├── Clone.java │ │ │ │ ├── Copy.java │ │ │ │ ├── CurrentNode.java │ │ │ │ ├── Login.java │ │ │ │ ├── Logout.java │ │ │ │ ├── Move.java │ │ │ │ ├── OrderBefore.java │ │ │ │ ├── ReadValue.java │ │ │ │ ├── Refresh.java │ │ │ │ ├── RemoveItem.java │ │ │ │ ├── RemoveItems.java │ │ │ │ ├── Rename.java │ │ │ │ ├── Save.java │ │ │ │ ├── SetBinaryProperty.java │ │ │ │ ├── SetMultivalueProperty.java │ │ │ │ └── SetProperty.java │ │ │ ├── ext/ │ │ │ │ ├── CreateWorkspace.java │ │ │ │ ├── StartJackrabbit.java │ │ │ │ ├── StartJackrabbitSingleton.java │ │ │ │ └── StopJackrabbit.java │ │ │ ├── fs/ │ │ │ │ ├── ExportFileSystem.java │ │ │ │ ├── ExportPropertyToFile.java │ │ │ │ ├── FileToInputStream.java │ │ │ │ └── ImportFileSystem.java │ │ │ ├── info/ │ │ │ │ ├── AbstractLs.java │ │ │ │ ├── AbstractLsItems.java │ │ │ │ ├── AbstractLsNodes.java │ │ │ │ ├── AbstractLsProperties.java │ │ │ │ ├── Cat.java │ │ │ │ ├── Describe.java │ │ │ │ ├── Dump.java │ │ │ │ ├── Help.java │ │ │ │ ├── Info.java │ │ │ │ ├── JcrInfoCommandException.java │ │ │ │ ├── LsCollectedItems.java │ │ │ │ ├── LsCollectedNodes.java │ │ │ │ ├── LsCollectedProperties.java │ │ │ │ ├── LsItems.java │ │ │ │ ├── LsNamespaces.java │ │ │ │ ├── LsNodes.java │ │ │ │ ├── LsProperties.java │ │ │ │ ├── LsReferences.java │ │ │ │ ├── LsVersions.java │ │ │ │ └── PrintHelper.java │ │ │ ├── lock/ │ │ │ │ ├── AddLockToken.java │ │ │ │ ├── Lock.java │ │ │ │ ├── RefreshLock.java │ │ │ │ ├── RemoveLockToken.java │ │ │ │ └── Unlock.java │ │ │ ├── mixin/ │ │ │ │ ├── AddMixin.java │ │ │ │ └── RemoveMixin.java │ │ │ ├── namespace/ │ │ │ │ ├── RegisterNamespace.java │ │ │ │ ├── SetNamespacePrefix.java │ │ │ │ └── UnregisterNamespace.java │ │ │ ├── nodetype/ │ │ │ │ └── RegisterNodeType.java │ │ │ ├── query/ │ │ │ │ ├── AbstractQuery.java │ │ │ │ ├── SQLQuery.java │ │ │ │ └── XPathQuery.java │ │ │ ├── version/ │ │ │ │ ├── AddVersionLabel.java │ │ │ │ ├── Checkin.java │ │ │ │ ├── Checkout.java │ │ │ │ ├── Merge.java │ │ │ │ ├── RemoveVersion.java │ │ │ │ ├── RemoveVersionByLabel.java │ │ │ │ ├── RemoveVersionLabel.java │ │ │ │ ├── Restore.java │ │ │ │ └── RestoreByLabel.java │ │ │ └── xml/ │ │ │ ├── AbstractExportViewToFile.java │ │ │ ├── ExportDocViewToFile.java │ │ │ ├── ExportSysViewToFile.java │ │ │ └── ImportXmlFromInputStream.java │ │ └── package-info.java │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── javax.jcr.RepositoryFactory │ ├── WEB-INF/ │ │ └── web.xml │ ├── logback-cli.xml │ ├── logback.xml │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── standalone/ │ └── cli/ │ ├── command-line-rules.xml │ ├── command-line.xml │ ├── command.xml │ ├── digester-rules.dtd │ └── resources.properties ├── jackrabbit-vfs-ext/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── vfs/ │ │ └── ext/ │ │ ├── ds/ │ │ │ ├── LazyFileContentInputStream.java │ │ │ ├── VFSBackend.java │ │ │ ├── VFSDataStore.java │ │ │ ├── VFSUtils.java │ │ │ └── package-info.java │ │ └── fs/ │ │ └── VFSFileSystem.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── vfs/ │ │ └── ext/ │ │ ├── TestAll.java │ │ ├── ds/ │ │ │ ├── TestVFSDataStore.java │ │ │ └── VFSTestUtils.java │ │ └── fs/ │ │ └── VFSFileSystemTest.java │ └── resources/ │ ├── log4j.properties │ ├── vfs-sftp.properties │ └── vfs-webdav.properties ├── jackrabbit-webapp/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── j2ee/ │ │ │ ├── AbstractConfig.java │ │ │ ├── BootstrapConfig.java │ │ │ ├── DerbyShutdown.java │ │ │ ├── Installer.java │ │ │ ├── JCRWebdavServerServlet.java │ │ │ ├── JNDIConfig.java │ │ │ ├── JcrApiNotFoundException.java │ │ │ ├── JcrRemotingServlet.java │ │ │ ├── RepositoryAccessServlet.java │ │ │ ├── RepositoryStartupServlet.java │ │ │ ├── ServletExceptionWithCause.java │ │ │ └── SimpleWebdavServlet.java │ │ ├── resources/ │ │ │ └── logback.xml │ │ └── webapp/ │ │ ├── META-INF/ │ │ │ ├── LICENSE │ │ │ └── NOTICE │ │ ├── WEB-INF/ │ │ │ ├── batchread.properties │ │ │ ├── config.xml │ │ │ ├── protectedHandlers.properties │ │ │ ├── templates/ │ │ │ │ └── bootstrap.properties │ │ │ └── web.xml │ │ ├── about.jsp │ │ ├── bootstrap/ │ │ │ ├── error.jsp │ │ │ ├── exists.jsp │ │ │ ├── missing.jsp │ │ │ ├── notexists.jsp │ │ │ ├── reconfigure.jsp │ │ │ ├── running.jsp │ │ │ └── success.jsp │ │ ├── css/ │ │ │ └── default.css │ │ ├── error/ │ │ │ ├── classpath.jsp │ │ │ └── repository.jsp │ │ ├── footer.jsp │ │ ├── header.jsp │ │ ├── index.jsp │ │ ├── local.jsp │ │ ├── remote.jsp │ │ ├── remoting/ │ │ │ ├── footer.jsp │ │ │ ├── header.jsp │ │ │ ├── index.jsp │ │ │ ├── json.js │ │ │ ├── read.jsp │ │ │ ├── read_batch.jsp │ │ │ ├── write.jsp │ │ │ ├── write_batch.jsp │ │ │ └── write_simple.jsp │ │ ├── search.jsp │ │ ├── swr.jsp │ │ ├── troubleshooting.jsp │ │ ├── webdav-jcr.jsp │ │ ├── webdav-remoting.jsp │ │ ├── webdav-simple.jsp │ │ └── welcome.jsp │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── j2ee/ │ │ └── TomcatIT.java │ └── resources/ │ └── logback-test.xml ├── jackrabbit-webdav/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── appended-resources/ │ │ │ └── META-INF/ │ │ │ └── NOTICE │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── webdav/ │ │ │ ├── AbstractLocatorFactory.java │ │ │ ├── ContentCodingAwareRequest.java │ │ │ ├── DavCompliance.java │ │ │ ├── DavConstants.java │ │ │ ├── DavException.java │ │ │ ├── DavLocatorFactory.java │ │ │ ├── DavMethods.java │ │ │ ├── DavResource.java │ │ │ ├── DavResourceFactory.java │ │ │ ├── DavResourceIterator.java │ │ │ ├── DavResourceIteratorImpl.java │ │ │ ├── DavResourceLocator.java │ │ │ ├── DavServletRequest.java │ │ │ ├── DavServletResponse.java │ │ │ ├── DavSession.java │ │ │ ├── DavSessionProvider.java │ │ │ ├── MultiStatus.java │ │ │ ├── MultiStatusResponse.java │ │ │ ├── Status.java │ │ │ ├── WebdavRequest.java │ │ │ ├── WebdavRequestContext.java │ │ │ ├── WebdavRequestImpl.java │ │ │ ├── WebdavResponse.java │ │ │ ├── WebdavResponseImpl.java │ │ │ ├── bind/ │ │ │ │ ├── BindConstants.java │ │ │ │ ├── BindInfo.java │ │ │ │ ├── BindServletRequest.java │ │ │ │ ├── BindableResource.java │ │ │ │ ├── ParentElement.java │ │ │ │ ├── ParentSet.java │ │ │ │ ├── RebindInfo.java │ │ │ │ ├── UnbindInfo.java │ │ │ │ └── package-info.java │ │ │ ├── client/ │ │ │ │ └── methods/ │ │ │ │ ├── BaseDavRequest.java │ │ │ │ ├── HttpBind.java │ │ │ │ ├── HttpCheckin.java │ │ │ │ ├── HttpCheckout.java │ │ │ │ ├── HttpCopy.java │ │ │ │ ├── HttpDelete.java │ │ │ │ ├── HttpLabel.java │ │ │ │ ├── HttpLock.java │ │ │ │ ├── HttpMerge.java │ │ │ │ ├── HttpMkcol.java │ │ │ │ ├── HttpMkworkspace.java │ │ │ │ ├── HttpMove.java │ │ │ │ ├── HttpOptions.java │ │ │ │ ├── HttpOrderpatch.java │ │ │ │ ├── HttpPoll.java │ │ │ │ ├── HttpPropfind.java │ │ │ │ ├── HttpProppatch.java │ │ │ │ ├── HttpRebind.java │ │ │ │ ├── HttpReport.java │ │ │ │ ├── HttpSearch.java │ │ │ │ ├── HttpSubscribe.java │ │ │ │ ├── HttpUnbind.java │ │ │ │ ├── HttpUnlock.java │ │ │ │ ├── HttpUnsubscribe.java │ │ │ │ ├── HttpUpdate.java │ │ │ │ ├── HttpVersionControl.java │ │ │ │ ├── XmlEntity.java │ │ │ │ └── package-info.java │ │ │ ├── header/ │ │ │ │ ├── CodedUrlHeader.java │ │ │ │ ├── DepthHeader.java │ │ │ │ ├── FieldValueParser.java │ │ │ │ ├── Header.java │ │ │ │ ├── IfHeader.java │ │ │ │ ├── LabelHeader.java │ │ │ │ ├── OverwriteHeader.java │ │ │ │ ├── PollTimeoutHeader.java │ │ │ │ ├── TimeoutHeader.java │ │ │ │ └── package-info.java │ │ │ ├── io/ │ │ │ │ ├── InputContext.java │ │ │ │ ├── InputContextImpl.java │ │ │ │ ├── OutputContext.java │ │ │ │ ├── OutputContextImpl.java │ │ │ │ └── package-info.java │ │ │ ├── lock/ │ │ │ │ ├── AbstractActiveLock.java │ │ │ │ ├── AbstractLockEntry.java │ │ │ │ ├── ActiveLock.java │ │ │ │ ├── DefaultActiveLock.java │ │ │ │ ├── LockDiscovery.java │ │ │ │ ├── LockEntry.java │ │ │ │ ├── LockInfo.java │ │ │ │ ├── LockManager.java │ │ │ │ ├── Scope.java │ │ │ │ ├── SimpleLockManager.java │ │ │ │ ├── SupportedLock.java │ │ │ │ ├── Type.java │ │ │ │ └── package-info.java │ │ │ ├── observation/ │ │ │ │ ├── DefaultEventType.java │ │ │ │ ├── EventBundle.java │ │ │ │ ├── EventDiscovery.java │ │ │ │ ├── EventType.java │ │ │ │ ├── Filter.java │ │ │ │ ├── ObservationConstants.java │ │ │ │ ├── ObservationDavServletRequest.java │ │ │ │ ├── ObservationDavServletResponse.java │ │ │ │ ├── ObservationResource.java │ │ │ │ ├── Subscription.java │ │ │ │ ├── SubscriptionDiscovery.java │ │ │ │ ├── SubscriptionInfo.java │ │ │ │ ├── SubscriptionManager.java │ │ │ │ └── package-info.java │ │ │ ├── ordering/ │ │ │ │ ├── OrderPatch.java │ │ │ │ ├── OrderingConstants.java │ │ │ │ ├── OrderingDavServletRequest.java │ │ │ │ ├── OrderingResource.java │ │ │ │ ├── OrderingType.java │ │ │ │ ├── Position.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── property/ │ │ │ │ ├── AbstractDavProperty.java │ │ │ │ ├── DavProperty.java │ │ │ │ ├── DavPropertyIterator.java │ │ │ │ ├── DavPropertyName.java │ │ │ │ ├── DavPropertyNameIterator.java │ │ │ │ ├── DavPropertyNameSet.java │ │ │ │ ├── DavPropertySet.java │ │ │ │ ├── DefaultDavProperty.java │ │ │ │ ├── HrefProperty.java │ │ │ │ ├── PropContainer.java │ │ │ │ ├── PropEntry.java │ │ │ │ ├── PropfindInfo.java │ │ │ │ ├── ProppatchInfo.java │ │ │ │ ├── ResourceType.java │ │ │ │ └── package-info.java │ │ │ ├── search/ │ │ │ │ ├── QueryGrammerSet.java │ │ │ │ ├── SearchConstants.java │ │ │ │ ├── SearchInfo.java │ │ │ │ ├── SearchResource.java │ │ │ │ └── package-info.java │ │ │ ├── security/ │ │ │ │ ├── AclProperty.java │ │ │ │ ├── AclResource.java │ │ │ │ ├── AclRestrictionsProperty.java │ │ │ │ ├── CurrentUserPrivilegeSetProperty.java │ │ │ │ ├── Principal.java │ │ │ │ ├── Privilege.java │ │ │ │ ├── SecurityConstants.java │ │ │ │ ├── SupportedPrivilege.java │ │ │ │ ├── SupportedPrivilegeSetProperty.java │ │ │ │ ├── package-info.java │ │ │ │ └── report/ │ │ │ │ ├── AbstractSecurityReport.java │ │ │ │ ├── AclPrincipalReport.java │ │ │ │ ├── PrincipalMatchReport.java │ │ │ │ ├── PrincipalSearchReport.java │ │ │ │ ├── SearchablePropertyReport.java │ │ │ │ └── package-info.java │ │ │ ├── server/ │ │ │ │ ├── AbstractWebdavServlet.java │ │ │ │ ├── WebdavRequestContextHolder.java │ │ │ │ ├── WebdavRequestContextImpl.java │ │ │ │ └── package-info.java │ │ │ ├── transaction/ │ │ │ │ ├── TransactionConstants.java │ │ │ │ ├── TransactionDavServletRequest.java │ │ │ │ ├── TransactionInfo.java │ │ │ │ ├── TransactionResource.java │ │ │ │ ├── TxActiveLock.java │ │ │ │ ├── TxLockEntry.java │ │ │ │ ├── TxLockManager.java │ │ │ │ └── package-info.java │ │ │ ├── util/ │ │ │ │ ├── CSRFUtil.java │ │ │ │ ├── EncodeUtil.java │ │ │ │ ├── HttpDateFormat.java │ │ │ │ ├── HttpDateTimeFormatter.java │ │ │ │ ├── LinkHeaderFieldParser.java │ │ │ │ └── package-info.java │ │ │ ├── version/ │ │ │ │ ├── ActivityResource.java │ │ │ │ ├── BaselineResource.java │ │ │ │ ├── DeltaVConstants.java │ │ │ │ ├── DeltaVResource.java │ │ │ │ ├── DeltaVServletRequest.java │ │ │ │ ├── LabelInfo.java │ │ │ │ ├── LabelSetProperty.java │ │ │ │ ├── MergeInfo.java │ │ │ │ ├── OptionsInfo.java │ │ │ │ ├── OptionsResponse.java │ │ │ │ ├── SupportedMethodSetProperty.java │ │ │ │ ├── UpdateInfo.java │ │ │ │ ├── VersionControlledResource.java │ │ │ │ ├── VersionHistoryResource.java │ │ │ │ ├── VersionResource.java │ │ │ │ ├── VersionableResource.java │ │ │ │ ├── WorkspaceResource.java │ │ │ │ ├── package-info.java │ │ │ │ └── report/ │ │ │ │ ├── AbstractReport.java │ │ │ │ ├── CompareBaselineReport.java │ │ │ │ ├── ExpandPropertyReport.java │ │ │ │ ├── LatestActivityVersionReport.java │ │ │ │ ├── LocateByHistoryReport.java │ │ │ │ ├── Report.java │ │ │ │ ├── ReportInfo.java │ │ │ │ ├── ReportType.java │ │ │ │ ├── SupportedReportSetProperty.java │ │ │ │ ├── VersionTreeReport.java │ │ │ │ └── package-info.java │ │ │ └── xml/ │ │ │ ├── DavDocumentBuilderFactory.java │ │ │ ├── DomUtil.java │ │ │ ├── ElementIterator.java │ │ │ ├── Namespace.java │ │ │ ├── ResultHelper.java │ │ │ ├── XmlSerializable.java │ │ │ └── package-info.java │ │ ├── javadoc/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── webdav/ │ │ │ ├── client/ │ │ │ │ └── methods/ │ │ │ │ └── package.html │ │ │ ├── lock/ │ │ │ │ └── package.html │ │ │ ├── observation/ │ │ │ │ └── package.html │ │ │ ├── ordering/ │ │ │ │ └── package.html │ │ │ ├── property/ │ │ │ │ └── package.html │ │ │ ├── search/ │ │ │ │ └── package.html │ │ │ ├── transaction/ │ │ │ │ └── package.html │ │ │ ├── util/ │ │ │ │ └── package.html │ │ │ ├── version/ │ │ │ │ ├── package.html │ │ │ │ └── report/ │ │ │ │ └── package.html │ │ │ └── xml/ │ │ │ └── package.html │ │ └── resources/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── webdav/ │ │ └── statuscode.properties │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── webdav/ │ │ ├── header/ │ │ │ ├── FieldValueParserTest.java │ │ │ └── TestAll.java │ │ ├── io/ │ │ │ ├── OutputContextImplTest.java │ │ │ └── TestAll.java │ │ ├── lock/ │ │ │ ├── ActiveLockTest.java │ │ │ └── TestAll.java │ │ ├── util/ │ │ │ ├── CSRFUtilTest.java │ │ │ ├── HttpDateTimeFormatterTest.java │ │ │ ├── LinkHeaderFieldParserTest.java │ │ │ └── TestAll.java │ │ └── xml/ │ │ ├── NamespaceTest.java │ │ ├── ParserTest.java │ │ └── TestAll.java │ └── resources/ │ └── logback-test.xml ├── pom.xml └── test/ ├── compatibility/ │ ├── README.txt │ ├── assembly.xml │ ├── base/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── jackrabbit/ │ │ │ └── harness/ │ │ │ └── compatibility/ │ │ │ └── AbstractRepositoryTest.java │ │ └── resources/ │ │ └── log4j.properties │ ├── create21/ │ │ ├── pom.xml │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── harness/ │ │ └── compatibility/ │ │ └── CreateRepositoryTest.java │ ├── create22/ │ │ ├── pom.xml │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── harness/ │ │ └── compatibility/ │ │ └── CreateRepositoryTest.java │ ├── create24/ │ │ ├── pom.xml │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── harness/ │ │ └── compatibility/ │ │ └── CreateRepositoryTest.java │ ├── create26/ │ │ ├── pom.xml │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── harness/ │ │ └── compatibility/ │ │ └── CreateRepositoryTest.java │ ├── parent/ │ │ └── pom.xml │ └── pom.xml ├── jackrabbit-bundle-it/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── test/ │ └── osgi/ │ └── BundleTest.java └── performance/ ├── README.txt ├── base/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── performance/ │ │ ├── AbstractDeepTreeTest.java │ │ ├── AbstractPerformanceTest.java │ │ ├── AbstractTest.java │ │ ├── AddGroupMembersTest.java │ │ ├── BigFileReadTest.java │ │ ├── BigFileWriteTest.java │ │ ├── ConcurrentReadAccessControlledTreeTest.java │ │ ├── ConcurrentReadDeepTreeTest.java │ │ ├── ConcurrentReadTest.java │ │ ├── ConcurrentReadWriteTest.java │ │ ├── CreateManyChildNodesTest.java │ │ ├── CreateUserTest.java │ │ ├── DescendantSearchTest.java │ │ ├── GroupGetMembersTest.java │ │ ├── GroupMemberLookupTest.java │ │ ├── LoginLogoutTest.java │ │ ├── LoginTest.java │ │ ├── PathBasedQueryTest.java │ │ ├── ReadDeepTreeTest.java │ │ ├── ReadPropertyTest.java │ │ ├── RefreshTest.java │ │ ├── SQL2DescendantSearchTest.java │ │ ├── SQL2SearchTest.java │ │ ├── SetPropertyTest.java │ │ ├── SimpleSearchTest.java │ │ ├── SmallFileReadTest.java │ │ ├── SmallFileWriteTest.java │ │ ├── TestInputStream.java │ │ ├── ThreeWayJoinTest.java │ │ ├── TransientManyChildNodesTest.java │ │ ├── TwoWayJoinTest.java │ │ └── UpdateManyChildNodesTest.java │ └── resources/ │ ├── deepTree.xml │ └── log4j.properties ├── jackrabbit21/ │ ├── pom.xml │ └── src/ │ └── test/ │ └── java/ │ └── org/ │ └── apache/ │ └── jackrabbit/ │ └── performance/ │ └── PerformanceTest.java ├── jackrabbit22/ │ ├── pom.xml │ └── src/ │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── performance/ │ │ └── PerformanceTest.java │ └── resources/ │ ├── btree-usermanager-repository.xml │ └── default-usermanager-repository.xml ├── jackrabbit23/ │ ├── pom.xml │ └── src/ │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── performance/ │ │ └── PerformanceTest.java │ └── resources/ │ ├── btree-usermanager-repository.xml │ └── default-usermanager-repository.xml ├── jackrabbit24/ │ ├── pom.xml │ └── src/ │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── performance/ │ │ └── PerformanceTest.java │ └── resources/ │ ├── btree-usermanager-repository.xml │ └── default-usermanager-repository.xml ├── jackrabbit26/ │ ├── pom.xml │ └── src/ │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── apache/ │ │ └── jackrabbit/ │ │ └── performance/ │ │ └── PerformanceTest.java │ └── resources/ │ ├── btree-usermanager-repository.xml │ └── default-usermanager-repository.xml ├── parent/ │ └── pom.xml ├── plot.sh └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .asf.yaml ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # https://cwiki.apache.org/confluence/x/7guYBw github: description: "Apache Jackrabbit" homepage: "https://jackrabbit.apache.org/" labels: - java - jackrabbit - jcr - repository - database autolink_jira: - OAK - JCR - JCRVLT - SLING - FELIX protected_branches: trunk: {} ================================================ FILE: .github/workflows/build.yml ================================================ # ~ Licensed to the Apache Software Foundation (ASF) under one # ~ or more contributor license agreements. See the NOTICE file # ~ distributed with this work for additional information # ~ regarding copyright ownership. The ASF licenses this file # ~ to you under the Apache License, Version 2.0 (the # ~ "License"); you may not use this file except in compliance # ~ with the License. You may obtain a copy of the License at # ~ # ~ http://www.apache.org/licenses/LICENSE-2.0 # ~ # ~ Unless required by applicable law or agreed to in writing, # ~ software distributed under the License is distributed on an # ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # ~ KIND, either express or implied. See the License for the # ~ specific language governing permissions and limitations # ~ under the License. name: Build on: push: branches: - trunk pull_request: types: [opened, synchronize, reopened] jobs: build: name: Maven Build runs-on: ubuntu-latest steps: - name: Git clone uses: actions/checkout@v6 - name: Set up JDK 11 uses: actions/setup-java@v5 with: java-version: 11 distribution: temurin cache: maven server-id: apache.snapshots.https # Value of the distributionManagement/repository/id field of the pom.xml server-username: MAVEN_APACHE_NEXUS_USERNAME # env variable for username in deploy server-password: MAVEN_APACHE_NEXUS_PASSWORD # env variable for token in deploy # sets environment variables to be used in subsequent steps: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable - name: Set environment variables shell: bash run: | if [ "${{github.ref}}" = "refs/heads/trunk" ] && [ "${{github.event_name}}" = "push" ] && [ "${{github.repository_owner}}" = "apache" ]; then echo 'Running on main branch of the canonical repo' echo "MVN_ADDITIONAL_OPTS=-DdeployAtEnd=true" >> $GITHUB_ENV echo "MVN_GOAL=deploy" >> $GITHUB_ENV echo "MAVEN_APACHE_NEXUS_USERNAME=${{ secrets.NEXUS_USER }}" >> $GITHUB_ENV echo "MAVEN_APACHE_NEXUS_PASSWORD=${{ secrets.NEXUS_PW }}" >> $GITHUB_ENV else echo 'Running outside main branch/canonical repo' echo "MVN_ADDITIONAL_OPTS=" >> $GITHUB_ENV echo "MVN_GOAL=install" >> $GITHUB_ENV fi - name: Build # executing ITs requires installing artifacts to the local repository run: mvn -B ${{ env.MVN_GOAL }} ${{ env.MVN_ADDITIONAL_OPTS }} -Pcoverage,integrationTesting,javadoc -Dorg.ops4j.pax.url.mvn.repositories="https://repo1.maven.org/maven2@id=central" - name: Upload build result uses: actions/upload-artifact@v6 with: name: compiled-classes-and-coverage # compare with https://docs.sonarsource.com/sonarqube-cloud/advanced-setup/languages/java/#java-analysis-and-bytecode path: | **/target/**/*.class **/target/site/jacoco*/*.xml # execute analysis in a separate job for better visualization and usage of matrix builds # https://docs.sonarsource.com/sonarcloud/advanced-setup/ci-based-analysis/sonarscanner-for-maven/#invoking-the-goal sonar: name: SonarQube Analysis runs-on: ubuntu-latest needs: build # not supported on forks, https://portal.productboard.com/sonarsource/1-sonarqube-cloud/c/50-sonarcloud-analyzes-external-pull-request if: ${{ github.repository == 'apache/jackrabbit' }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK 21 uses: actions/setup-java@v5 with: java-version: 21 distribution: temurin cache: maven - name: Download compiled classes uses: actions/download-artifact@v4 with: name: compiled-classes-and-coverage - name: Cache SonarQube packages uses: actions/cache@v5 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Analyze with SonarQube env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} run: SONAR_SCANNER_JAVA_OPTS="-Xmx8g" mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:5.5.0.6356:sonar -Dsonar.projectKey=apache_jackrabbit -Dsonar.organization=apache -Dsonar.scanner.skipJreProvisioning=true ================================================ FILE: .gitignore ================================================ target .classpath .project .settings *.iml *.ipr *.iws .idea ================================================ FILE: .mvn/README.md ================================================ Used for determining [`maven.multiModuleProjectDirectory`](https://issues.apache.org/jira/browse/MNG-5786). ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) 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. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. APACHE JACKRABBIT SUBCOMPONENTS Apache Jackrabbit includes parts with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the following licenses: XPath 2.0/XQuery 1.0 Parser: http://www.w3.org/2002/11/xquery-xpath-applets/xgrammar.zip Copyright (C) 2002 World Wide Web Consortium, (Massachusetts Institute of Technology, European Research Consortium for Informatics and Mathematics, Keio University). All Rights Reserved. This work is distributed under the W3C(R) Software License in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. W3C(R) SOFTWARE NOTICE AND LICENSE http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 This work (and included software, documentation such as READMEs, or other related items) is being provided by the copyright holders under the following license. By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions. Permission to copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications: 1. The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. 2. Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software Short Notice should be included (hypertext is preferred, text is permitted) within the body of any redistributed or derivative code. 3. Notice of any changes or modifications to the files, including the date changes were made. (We recommend you provide URIs to the location from which the code is derived.) THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders. ================================================ FILE: NOTICE.txt ================================================ Apache Jackrabbit Copyright 2014 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). Based on source code originally developed by Day Software (http://www.day.com/). ================================================ FILE: README.txt ================================================ ============================================================= Welcome to Apache Jackrabbit ============================================================= Apache Jackrabbit is a fully conforming implementation of the Content Repository for Java Technology API (JCR, specified in JSR 170 and 283). A content repository is a hierarchical content store with support for structured and unstructured content, full text search, versioning, transactions, observation, and more. Apache Jackrabbit is a project of the Apache Software Foundation. Building Jackrabbit =================== You can build Jackrabbit like this: mvn clean install You need Maven 3 (or higher) with Java 11 (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 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 this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Mailing Lists ============= To get involved with the Apache Jackrabbit project, start by having a look at our website and joining our mailing lists. For more details about Jackrabbit mailing lists as well as links to list archives, please see: http://jackrabbit.apache.org/mailing-lists.html Latest development ================== The latest Jackrabbit source code is available at https://github.com/apache/jackrabbit Credits ======= See http://jackrabbit.apache.org/jackrabbit-team.html for the list of Jackrabbit committers and main contributors. SonarQube Cloud =========== The CI builds automatically checks the code with SonarQube Cloud. The results are accessible at https://sonarcloud.io/project/overview?id=apache_jackrabbit ================================================ FILE: RELEASE-NOTES.txt ================================================ Release Notes -- Apache Jackrabbit -- Version 2.23.4-beta Introduction ------------ This is Apache Jackrabbit(TM) 2.23.4-beta, 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.23.4-beta is an unstable release cut directly from Jackrabbit trunk, with a focus on new features and other improvements. For production use we recommend the latest stable 2.20.x release. Changes in Jackrabbit 2.23.4-beta ---------------------------------- Bug [JCR-5226] - Base64 decoder does not handle input without padding correctly [JCR-5229] - LockedWrapperTest.testSequenceWithSessionRefresh() may fail due to race conditions Improvement [JCR-4521] - upgrade to commons-dbcp2 Test [JCR-4945] - Ensure OSGi-enabled Jackrabbit bundles deploy in environments featuring only Slf4j v2 or even Tika v2.9 Task [JCR-5211] - package-version jcr2spi [JCR-5216] - Update Jackrabbit Trunk to Oak 1.90.0 [JCR-5217] - Update Mockito dependency to 5.23.0 [JCR-5218] - webapp: bump htmlunit to 4.21.0 [JCR-5219] - set baseline comparisonVersion to latest stable (2.22.3) [JCR-5223] - update Apache parent pom to version 37 [JCR-5224] - remove jackrabbit 2.0 compatibility and performance tests [JCR-5230] - improve diagnostics in IndexingQueueTest [JCR-5232] - Update Jackrabbit Trunk to Oak 1.92.0 For more detailed information about all the changes in this and other Jackrabbit releases, please see the Jackrabbit issue tracker at https://issues.apache.org/jira/browse/JCR Release Contents ---------------- This release consists of a single source archive packaged as a zip file. The archive can be unpacked with the jar tool from your JDK installation. See the README.txt file for instructions on how to build this release. The source archive is accompanied by an SHA512 checksum and a PGP signature that you can use to verify the authenticity of your download. The public key used for the PGP signature can be found at https://www.apache.org/dist/jackrabbit/KEYS. About Apache Jackrabbit ----------------------- Apache Jackrabbit is a fully conforming implementation of the Content Repository for Java Technology API (JCR). A content repository is a hierarchical content store with support for structured and unstructured content, full text search, versioning, transactions, observation, and more. For more information, visit http://jackrabbit.apache.org/ About The Apache Software Foundation ------------------------------------ Established in 1999, The Apache Software Foundation provides organizational, 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 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. ================================================ FILE: assembly.xml ================================================ src zip ${project.basedir} **/target/** **/.*/** ================================================ FILE: examples/jackrabbit-firsthops/pom.xml ================================================ 4.0.0 org.apache.jackrabbit jackrabbit-firsthops 0.1-SNAPSHOT First Hops First Hops Example Page http://jackrabbit.apache.org/first-hops.html javax.jcr jcr 2.0 org.apache.jackrabbit jackrabbit-core 2.12.1 org.slf4j slf4j-log4j12 org.apache.maven.plugins maven-compiler-plugin 2.3.2 1.5 1.5 1.5 true false true false ================================================ FILE: examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/FirstHop.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jackrabbit.firsthops; import javax.jcr.GuestCredentials; import javax.jcr.Repository; import javax.jcr.Session; import org.apache.jackrabbit.commons.JcrUtils; /** * First hop example. Logs in to a content repository and prints a status * message. */ public class FirstHop { /** * The main entry point of the example application. * * @param args * command line arguments (ignored) * @throws Exception * if an error occurs */ public static void main(String[] args) throws Exception { Repository repository = JcrUtils.getRepository(); Session session = repository.login(new GuestCredentials()); try { String user = session.getUserID(); String name = repository.getDescriptor(Repository.REP_NAME_DESC); System.out.println("Logged in as " + user + " to a " + name + " repository."); } finally { session.logout(); } } } ================================================ FILE: examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/SecondHop.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jackrabbit.firsthops; import javax.jcr.Repository; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.Node; import org.apache.jackrabbit.commons.JcrUtils; /** * Second hop example. Stores, retrieves, and removes example content. */ public class SecondHop { /** * The main entry point of the example application. * * @param args * command line arguments (ignored) * @throws Exception * if an error occurs */ public static void main(String[] args) throws Exception { Repository repository = JcrUtils.getRepository(); Session session = repository.login(new SimpleCredentials("admin", "admin".toCharArray())); try { Node root = session.getRootNode(); // Store content Node hello = root.addNode("hello"); Node world = hello.addNode("world"); world.setProperty("message", "Hello, World!"); session.save(); // Retrieve content Node node = root.getNode("hello/world"); System.out.println(node.getPath()); System.out.println(node.getProperty("message").getString()); // Remove content root.getNode("hello").remove(); session.save(); } finally { session.logout(); } } } ================================================ FILE: examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/ThirdHop.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jackrabbit.firsthops; import javax.jcr.*; 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. */ public class ThirdHop { /** * The main entry point of the example application. * * @param args * command line arguments (ignored) * @throws Exception * if an error occurs */ public static void main(String[] args) throws Exception { Repository repository = JcrUtils.getRepository(); Session session = repository.login(new SimpleCredentials("admin", "admin".toCharArray())); FileInputStream xml = new FileInputStream("src/main/resources/test.xml"); try { Node root = session.getRootNode(); // Import the XML file unless already imported if (!root.hasNode("importxml")) { System.out.print("Importing xml... "); // Create an unstructured node under which to import the XML Node node = root.addNode("importxml", "nt:unstructured"); // Import the file "test.xml" under the created node session.importXML(node.getPath(), xml, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW); session.save(); System.out.println("done."); } dump(root); } finally { session.logout(); } } /** Recursively outputs the contents of the given node. */ private static void dump(Node node) throws RepositoryException { // First output the node path System.out.println(node.getPath()); // Skip the virtual (and large!) jcr:system subtree if (node.getName().equals("jcr:system")) { return; } // Then output the properties PropertyIterator properties = node.getProperties(); while (properties.hasNext()) { Property property = properties.nextProperty(); if (property.getDefinition().isMultiple()) { // A multi-valued property, print all values Value[] values = property.getValues(); for (int i = 0; i < values.length; i++) { System.out.println(property.getPath() + " = " + values[i].getString()); } } else { // A single-valued property System.out.println(property.getPath() + " = " + property.getString()); } } // Finally output all the child nodes recursively NodeIterator nodes = node.getNodes(); while (nodes.hasNext()) { dump(nodes.nextNode()); } } } ================================================ FILE: examples/jackrabbit-firsthops/src/main/resources/log4j.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. log4j.rootLogger=ERROR, Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d %p %c - %m%n ================================================ FILE: examples/jackrabbit-firsthops/src/main/resources/test.xml ================================================ Three Namespaces An Ellipse and a Rectangle The equation for ellipses 1 x 2 a 2 y 2 b 2 Last Modified January 10, 2002 ================================================ FILE: jackrabbit-aws-ext/README.txt ================================================ ==================================================== 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. ================================================ FILE: jackrabbit-aws-ext/pom.xml ================================================ 4.0.0 org.apache.jackrabbit jackrabbit-parent 2.23.5-beta-SNAPSHOT ../jackrabbit-parent/pom.xml jackrabbit-aws-ext Jackrabbit AWS Extension Jackrabbit extenstion to Amazon Webservices bundle javax.jcr jcr org.osgi org.osgi.annotation provided org.apache.jackrabbit jackrabbit-jcr-commons ${project.version} com.amazonaws aws-java-sdk-s3 1.12.791 org.apache.jackrabbit jackrabbit-data ${project.version} org.apache.jackrabbit jackrabbit-data ${project.version} test-jar test org.slf4j slf4j-api commons-io commons-io junit junit test org.slf4j slf4j-reload4j test maven-surefire-plugin **/aws/**/TestAll.java org.apache.felix maven-bundle-plugin true org.apache.jackrabbit.aws.ext.ds sun.io org.apache.rat apache-rat-plugin .checkstyle ================================================ FILE: jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3Constants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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"; /** * Constant to set proxy host. */ public static final String PROXY_HOST = "proxyHost"; /** * Constant to set proxy port. */ public static final String PROXY_PORT = "proxyPort"; /** * Path style access flag true/false */ public static final String S3_PATH_STYLE_ACCESS = "pathStyleAccess"; /** * private constructor so that class cannot initialized from outside. */ private S3Constants() { } } ================================================ FILE: jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3RequestDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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; } } ================================================ FILE: jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/Utils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AmazonClientException; import com.amazonaws.ClientConfiguration; import com.amazonaws.Protocol; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.S3ClientOptions; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.Region; import com.amazonaws.services.s3.model.S3ObjectSummary; import com.amazonaws.util.StringUtils; /** * 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) { String accessKey = prop.getProperty(S3Constants.ACCESS_KEY); String secretKey = prop.getProperty(S3Constants.SECRET_KEY); AmazonS3Client s3service = null; if (StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey)) { LOG.info("Configuring Amazon Client from environment"); s3service = new AmazonS3Client(getClientConfiguration(prop)); } else { LOG.info("Configuring Amazon Client from property file."); AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); 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 (StringUtils.isNullOrEmpty(region)) { com.amazonaws.regions.Region s3Region = Regions.getCurrentRegion(); if (s3Region != null) { region = s3Region.getName(); } else { throw new AmazonClientException( "parameter [" + S3Constants.S3_REGION + "] not configured and cannot be derived from environment"); } } 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); s3service.setS3ClientOptions(getS3ClientOptions(prop)); 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)); String protocol = prop.getProperty(S3Constants.S3_CONN_PROTOCOL); String proxyHost = prop.getProperty(S3Constants.PROXY_HOST); String proxyPort = prop.getProperty(S3Constants.PROXY_PORT); ClientConfiguration cc = new ClientConfiguration(); if (protocol != null && protocol.equalsIgnoreCase("http")) { cc.setProtocol(Protocol.HTTP); } if (proxyHost != null && !proxyHost.isEmpty()) { cc.setProxyHost(proxyHost); } if (proxyPort != null && !proxyPort.isEmpty()) { cc.setProxyPort(Integer.parseInt(proxyPort)); } cc.setConnectionTimeout(connectionTimeOut); cc.setSocketTimeout(socketTimeOut); cc.setMaxConnections(maxConnections); cc.setMaxErrorRetry(maxErrorRetry); return cc; } private static S3ClientOptions getS3ClientOptions(Properties prop) { boolean pathStyleAccess = Boolean.parseBoolean(prop.getProperty(S3Constants.S3_PATH_STYLE_ACCESS)); return S3ClientOptions.builder().setPathStyleAccess(pathStyleAccess).build(); } } ================================================ FILE: jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3Backend.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AbstractBackend; 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.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.regions.Regions; 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.S3ObjectInputStream; 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; import com.amazonaws.util.StringUtils; /** * A data store backend that stores data on Amazon S3. */ public class S3Backend extends AbstractBackend { /** * 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 Properties properties; private Date startTime; 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 { super.init(store, homeDir, config); 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"); setDataStore(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 (StringUtils.isNullOrEmpty(region)) { com.amazonaws.regions.Region ec2Region = Regions.getCurrentRegion(); if (ec2Region != null) { s3Region = Region.fromValue(ec2Region.getName()); } else { throw new AmazonClientException( "parameter [" + S3Constants.S3_REGION + "] not configured and cannot be derived from environment"); } } else { 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; } setAsyncWritePoolSize(asyncWritePoolSize); 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"); } getAsyncWriteExecutor().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.trace("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()); getAsyncWriteExecutor().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.trace("[{}] 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); S3ObjectInputStream s3in = object.getObjectContent(); InputStream in = new S3BackendResourceAbortableInputStream(s3in); 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 getAllIdentifiers() throws DataStoreException { long start = System.currentTimeMillis(); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader( getClass().getClassLoader()); Set ids = new HashSet(); 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 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 deleteIdSet = new HashSet(30); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader( getClass().getClassLoader()); ObjectListing prevObjectListing = s3service.listObjects(bucket); while (true) { List deleteList = new ArrayList(); 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 && getDataStore().confirmDelete(identifier) // confirm once more that record's lastModified < min // order is important here && s3service.getObjectMetadata(bucket, s3ObjSumm.getKey()).getLastModified().getTime() < min) { getDataStore().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() throws DataStoreException { super.close(); // backend is closing. abort all mulitpart uploads from start. 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 deleteList = new ArrayList(); 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); } } } } ================================================ FILE: jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3BackendResourceAbortableInputStream.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.apache.commons.io.input.ProxyInputStream; import org.apache.jackrabbit.core.data.BackendResourceAbortable; import com.amazonaws.services.s3.model.S3ObjectInputStream; /** * S3 Backend based InputStream wrapper to implement {@link BackendResourceAbortable}. */ public class S3BackendResourceAbortableInputStream extends ProxyInputStream implements BackendResourceAbortable { /** * Underlying backend {@link S3ObjectInputStream} instance. */ private final S3ObjectInputStream s3input; /** * Construct with the given backend {@link S3ObjectInputStream} instance. * @param s3input */ public S3BackendResourceAbortableInputStream(final S3ObjectInputStream s3input) { super(s3input); this.s3input = s3input; } @Override public void abort() { s3input.abort(); } } ================================================ FILE: jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3DataStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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; } } ================================================ FILE: jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/package-info.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* see JCR-4060 */ @org.osgi.annotation.versioning.Version("2.14.0") package org.apache.jackrabbit.aws.ext.ds; ================================================ FILE: jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/TestAll.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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); /** * TestAll 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; } } ================================================ FILE: jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/S3TestDataStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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; } } ================================================ FILE: jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSAsyncTouch.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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; } } ================================================ FILE: jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSSES3.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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()); } } } ================================================ FILE: jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSmallCache.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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; } } ================================================ FILE: jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3Ds.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 { 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 deleteList = new ArrayList(); 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(); } } ================================================ FILE: jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DsCacheOff.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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; } } ================================================ FILE: jackrabbit-aws-ext/src/test/resources/aws.properties ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 # proxy configurations (optional) proxyHost= proxyPort= ================================================ FILE: jackrabbit-aws-ext/src/test/resources/log4j.properties ================================================ # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 ================================================ FILE: jackrabbit-aws-ext/src/test/resources/repository_sample.xml ================================================ ================================================ FILE: jackrabbit-core/HEADER.txt ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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: jackrabbit-core/README.txt ================================================ ========================== Welcome to Jackrabbit Core ========================== This is the Core component of the Apache Jackrabbit project. This component contains the core of the fully JSR-170 compliant Apache Jackrabbit content repository implementation. ================================================ FILE: jackrabbit-core/checkstyle-suppressions.xml ================================================ ================================================ FILE: jackrabbit-core/checkstyle.xml ================================================ ================================================ FILE: jackrabbit-core/jdepend.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ignore.java=java.*,javax.*,org.w3c.*,org.xml.* ignore.sun=sun.*,com.sun.* ignore.jackrabbit=org.apache.jackrabbit.util ignore.slf4j=org.slf4j ignore.commons=org.apache.commons ignore.concurrent=EDU.* ================================================ FILE: jackrabbit-core/pom.xml ================================================ 4.0.0 org.apache.jackrabbit jackrabbit-parent 2.23.5-beta-SNAPSHOT ../jackrabbit-parent/pom.xml jackrabbit-core Jackrabbit Core Jackrabbit content repository implementation false -Xmx512m maven-antrun-plugin process-test-resources process-test-resources run maven-surefire-plugin **/*TestAll.java ${test.opts} java.awt.headless true derby.system.durability test derby.storage.fileSyncTransactionLog true derby.stream.error.file target/derby.log org.apache.jackrabbit.repository.home target/repository-factory-test known.issues 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 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 org.apache.jackrabbit.test.api.ShareableNodeTest#testTransientMoveShareableNode 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.data.GarbageCollectorTest#testSimultaneousRunGC org.apache.jackrabbit.core.ReplacePropertyWhileOthersReadTest org.apache.jackrabbit.core.security.user.MembershipCachePerfTest org.apache.jackrabbit.test.api.query.qom.NodeLocalNameTest#testStringLiteralInvalidName org.apache.jackrabbit.test.api.query.qom.NodeLocalNameTest#testPathLiteral org.apache.jackrabbit.test.api.query.qom.NodeLocalNameTest#testURILiteral org.apache.jackrabbit.core.version.ModifyNonVersionableCheckedOutTest#testNonVersionableCheckedOut org.apache.jackrabbit.core.version.ModifyNonVersionableCheckedOutTest#testModifyNonVersionableNodeWithCheckedOutProperty org.apache.jackrabbit.core.AddMoveTest#testTopLevelAddRemove org.apache.jackrabbit.core.config.SecurityConfigTest#testInvalidConfig org.apache.jackrabbit.core.nodetype.CyclicNodeTypeRegistrationTest org.apache.jackrabbit.core.query.lucene.SearchIndexConsistencyCheckTest#testDoubleCheckStressTest org.apache.jackrabbit.core.version.RemoveAndAddVersionLabelXATest#testVersionLabel org.apache.jackrabbit.test.integration ${org.apache.jackrabbit.test.integration} do_test integration-test **/integration/*Test.java test org.apache.rat apache-rat-plugin src/main/javadoc/**/*.uxf src/test/repository/** src/test/resources/**/*.txt src/test/resources/**/*.rtf src/test/resources/**/*.cnd src/test/compatibility/**/target/** src/test/compatibility/**/.*/** src/test/compatibility/repositories.zip repository/** *.log org.apache.maven.plugins maven-jar-plugin logback-test.xml test-jar src/main/resources src/main/resources-filtered true org.eclipse.m2e lifecycle-mapping 1.0.0 org.apache.maven.plugins maven-antrun-plugin [3,) run concurrent concurrent org.apache.commons commons-collections4 commons-io commons-io org.apache.commons commons-dbcp2 2.14.0 javax.jcr jcr org.apache.jackrabbit oak-jackrabbit-api ${oak-jackrabbit-api.version.implemented} org.apache.jackrabbit jackrabbit-jcr-commons ${project.version} org.apache.jackrabbit jackrabbit-data ${project.version} org.apache.jackrabbit jackrabbit-data ${project.version} test-jar test org.apache.jackrabbit jackrabbit-spi-commons ${project.version} org.apache.jackrabbit jackrabbit-spi ${project.version} org.apache.jackrabbit jackrabbit-spi ${project.version} tests test org.apache.tika tika-core org.slf4j slf4j-api org.apache.lucene lucene-core org.apache.derby derby test org.apache.derby derbytools test org.apache.jackrabbit jackrabbit-jcr-tests ${project.version} true junit junit test org.apache.jackrabbit jackrabbit-jcr-benchmark test org.apache.tika tika-parsers-standard-package test ch.qos.logback logback-classic test javax.transaction javax.transaction-api test com.h2database h2 2.4.240 test org.mockito mockito-core test integrationTesting true mssql jackrabbit org.apache.jackrabbit.core.fs.db.MSSqlFileSystem org.apache.jackrabbit.core.data.db.DbDataStore org.apache.jackrabbit.core.persistence.pool.MSSqlPersistenceManager org.apache.jackrabbit.core.journal.MSSqlDatabaseJournal mssql select 1 user pwd net.sourceforge.jtds.jdbc.Driver jdbc:jtds:sqlserver://localhost:2433/${config.db.name} jdbc:jtds:sqlserver://localhost:2433/master drop database ${config.db.name} create database ${config.db.name} oracle unused org.apache.jackrabbit.core.fs.db.OracleFileSystem org.apache.jackrabbit.core.data.db.DbDataStore org.apache.jackrabbit.core.persistence.pool.OraclePersistenceManager org.apache.jackrabbit.core.journal.OracleDatabaseJournal oracle select 'validationQuery' from dual user password oracle.jdbc.driver.OracleDriver jdbc:oracle:thin:@localhost:1521:xe unused unused unused h2 jackrabbit org.apache.jackrabbit.core.fs.db.DbFileSystem org.apache.jackrabbit.core.data.db.DbDataStore org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager org.apache.jackrabbit.core.journal.DatabaseJournal h2 call 1 sa sa org.h2.Driver jdbc:h2:~/jackrabbit2;MAX_LENGTH_INPLACE_LOB=10240;DB_CLOSE_ON_EXIT=FALSE unused drop all objects delete files unused use-descriptor-overlay org.codehaus.mojo sql-maven-plugin net.sourceforge.jtds jtds 1.2.2 provided ${config.db.driver} ${config.db.metaurl} ${config.db.user} ${config.db.pwd} sensibleKey drop-db clean execute true ${config.db.dropcommand} continue create-db clean execute true ${config.db.createcommand} maven-antrun-plugin overlay-repository-descriptors process-test-resources run net.sourceforge.jtds jtds 1.2.2 test com.oracle ojdbc14 10.2.0.3.0 test ================================================ FILE: jackrabbit-core/src/main/appended-resources/META-INF/NOTICE ================================================ Based on source code originally developed by Day Software (http://www.day.com/). ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AbstractNodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.NodeDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.NodeState; /** * Data object representing a node. */ public abstract class AbstractNodeData extends ItemData { /** Primary parent id of a shareable node. */ private NodeId primaryParentId; /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ protected AbstractNodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); if (state.isShareable()) { this.primaryParentId = state.getParentId(); } } /** * Create a new instance of this class. * * @param id item id */ protected AbstractNodeData(ItemId id) { super(id); } /** * Return the associated node state. * * @return node state */ public NodeState getNodeState() { return (NodeState) getState(); } /** * Return the associated node definition. * * @return node definition * @throws RepositoryException if the definition cannot be retrieved. */ public NodeDefinition getNodeDefinition() throws RepositoryException { return (NodeDefinition) getDefinition(); } /** * Sets the associated node definition. * * @param definition new node definition */ public void setNodeDefinition(NodeDefinition definition) { setDefinition(definition); } /** * Return the parent id of this node. Every shareable node in a shared set * has a different parent. * * @return parent id */ @Override public NodeId getParentId() { if (primaryParentId != null) { return primaryParentId; } return getState().getParentId(); } /** * Return the primary parent id of this node. Every shareable node in a * shared set has a different primary parent. Returns null * for nodes that are not shareable. * * @return primary parent id or null */ public NodeId getPrimaryParentId() { return primaryParentId; } /** * Set the primary parent id of this node. * * @param primaryParentId primary parent id */ protected void setPrimaryParentId(NodeId primaryParentId) { this.primaryParentId = primaryParentId; } /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AddMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.util.HashSet; import java.util.Set; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; /** * Session operation for adding a mixin type to a node. */ class AddMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public AddMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. if (MIX_VERSIONABLE.equals(mixinName) || MIX_SIMPLE_VERSIONABLE.equals(mixinName)) { permissions |= Permission.VERSION_MNGMT; } context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, permissions); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (!mixin.isMixin()) { throw new RepositoryException( context.getJCRName(mixinName) + " is not a mixin node type"); } Name primaryTypeName = node.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(mixinName)) { // new mixin is already included in primary type return this; } // build effective node type of mixin's & primary type in order // to detect conflicts NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet( node.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including // existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(mixinName)) { // new mixin is already included in existing mixin type(s) return this; } // add new mixin mixins.add(mixinName); // try to build new effective node type (will throw in case // of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // do the actual modifications implied by the new mixin; // try to revert the changes in case an exception occurs try { // modify the state of this node NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // add mixin name Set mixins = new HashSet(thisState.getMixinTypeNames()); mixins.add(mixinName); thisState.setMixinTypeNames(mixins); // set jcr:mixinTypes property node.setMixinTypesProperty(mixins); // add 'auto-create' properties defined in mixin type for (PropertyDefinition aPda : mixin.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; // make sure that the property is not already defined // by primary type or existing mixin's NodeTypeImpl declaringNT = (NodeTypeImpl) pd.getDeclaringNodeType(); if (!entExisting.includesNodeType(declaringNT.getQName())) { node.createChildProperty( pd.unwrap().getName(), pd.getRequiredType(), pd); } } // recursively add 'auto-create' child nodes defined in mixin type for (NodeDefinition aNda : mixin.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; // make sure that the child node is not already defined // by primary type or existing mixin's NodeTypeImpl declaringNT = (NodeTypeImpl) nd.getDeclaringNodeType(); if (!entExisting.includesNodeType(declaringNT.getQName())) { node.createChildNode( nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } } } catch (RepositoryException re) { // try to undo the modifications by removing the mixin try { node.removeMixin(mixinName); } catch (RepositoryException re1) { // silently ignore & fall through } throw re; } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.addMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.ReferentialIntegrityException; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.version.VersionException; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.retention.RetentionRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.state.UpdatableItemStateManager; import org.apache.jackrabbit.core.util.ReferenceChangeTracker; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * BatchedItemOperations is an internal helper class that * provides both high- and low-level operations directly on the * ItemState level. */ public class BatchedItemOperations extends ItemValidator { private static Logger log = LoggerFactory.getLogger(BatchedItemOperations.class); // flags used by the copy(...) methods protected static final int COPY = 0; protected static final int CLONE = 1; protected static final int CLONE_REMOVE_EXISTING = 2; /** * wrapped item state manager */ protected final UpdatableItemStateManager stateMgr; /** * current session used for checking access rights */ protected final SessionImpl session; private final HierarchyManager hierMgr; /** * Creates a new BatchedItemOperations instance. * * @param stateMgr item state manager * @param sessionContext the session context * @throws RepositoryException */ public BatchedItemOperations( UpdatableItemStateManager stateMgr, SessionContext sessionContext) throws RepositoryException { super(sessionContext); this.stateMgr = stateMgr; this.session = sessionContext.getSessionImpl(); this.hierMgr = sessionContext.getHierarchyManager(); } //-----------------------------------------< controlling batch operations > /** * Starts an edit operation on the wrapped state manager. * At the end of this operation, either {@link #update} or {@link #cancel} * must be invoked. * * @throws IllegalStateException if the state manager is already in edit mode */ public void edit() throws IllegalStateException { stateMgr.edit(); } /** * Store an item state. * * @param state item state that should be stored * @throws IllegalStateException if the manager is not in edit mode. */ public void store(ItemState state) throws IllegalStateException { stateMgr.store(state); } /** * Destroy an item state. * * @param state item state that should be destroyed * @throws IllegalStateException if the manager is not in edit mode. */ public void destroy(ItemState state) throws IllegalStateException { stateMgr.destroy(state); } /** * End an update operation. This will save all changes made since * the last invocation of {@link #edit()}. If this operation fails, * no item will have been saved. * * @throws RepositoryException if the update operation failed * @throws IllegalStateException if the state manager is not in edit mode */ public void update() throws RepositoryException, IllegalStateException { try { stateMgr.update(); } catch (ItemStateException ise) { String msg = "update operation failed"; log.debug(msg, ise); throw new RepositoryException(msg, ise); } } /** * Cancel an update operation. This will undo all changes made since * the last invocation of {@link #edit()}. * * @throws IllegalStateException if the state manager is not in edit mode */ public void cancel() throws IllegalStateException { stateMgr.cancel(); } //-------------------------------------------< high-level item operations > /** * Clones the subtree at the node srcAbsPath in to the new * location at destAbsPath. This operation is only supported: * * If the source element has the mixin mix:shareable (or some * derived node type) * If the parent node of destAbsPath has not already a shareable * node in the same shared set as the node at srcPath. * * * @param srcPath source path * @param destPath destination path * @return the node id of the destination's parent * * @throws ConstraintViolationException if the operation would violate a * node-type or other implementation-specific constraint. * @throws VersionException if the parent node of destAbsPath is * versionable and checked-in, or is non-versionable but its nearest versionable ancestor is * checked-in. This exception will also be thrown if removeExisting is true, * and a UUID conflict occurs that would require the moving and/or altering of a node that is checked-in. * @throws AccessDeniedException if the current session does not have * sufficient access rights to complete the operation. * @throws PathNotFoundException if the node at srcAbsPath in * srcWorkspace or the parent of destAbsPath in this workspace does not exist. * @throws ItemExistsException if a property already exists at * destAbsPath or a node already exist there, and same name * siblings are not allowed or if removeExisting is false and a * UUID conflict occurs. * @throws LockException if a lock prevents the clone. * @throws RepositoryException if the last element of destAbsPath * has an index or if another error occurs. */ public NodeId clone(Path srcPath, Path destPath) throws ConstraintViolationException, AccessDeniedException, VersionException, PathNotFoundException, ItemExistsException, LockException, RepositoryException, IllegalStateException { // check precondition checkInEditMode(); // 1. check paths & retrieve state NodeState srcState = getNodeState(srcPath); Path destParentPath = destPath.getAncestor(1); NodeState destParentState = getNodeState(destParentPath); int ind = destPath.getIndex(); if (ind > 0) { // subscript in name element String msg = "invalid destination path: " + safeGetJCRPath(destPath) + " (subscript in name element is not allowed)"; log.debug(msg); throw new RepositoryException(msg); } return clone(srcState, destParentState, destPath.getName()); } /** * Implementation of {@link #clone(Path, Path)} that has already determined * the affected NodeStates. * * @param srcState source state * @param destParentState destination parent state * @param destName destination name * @return the node id of the destination's parent * * @throws ConstraintViolationException if the operation would violate a * node-type or other implementation-specific constraint. * @throws VersionException if the parent node of destAbsPath is * versionable and checked-in, or is non-versionable but its nearest versionable ancestor is * checked-in. This exception will also be thrown if removeExisting is true, * and a UUID conflict occurs that would require the moving and/or altering of a node that is checked-in. * @throws AccessDeniedException if the current session does not have * sufficient access rights to complete the operation. * @throws PathNotFoundException if the node at srcAbsPath in * srcWorkspace or the parent of destAbsPath in this workspace does not exist. * @throws ItemExistsException if a property already exists at * destAbsPath or a node already exist there, and same name * siblings are not allowed or if removeExisting is false and a * UUID conflict occurs. * @throws LockException if a lock prevents the clone. * @throws RepositoryException if the last element of destAbsPath * has an index or if another error occurs. * @see #clone(Path, Path) */ public NodeId clone(NodeState srcState, NodeState destParentState, Name destName) throws ConstraintViolationException, AccessDeniedException, VersionException, PathNotFoundException, ItemExistsException, LockException, RepositoryException, IllegalStateException { // 2. check access rights, lock status, node type constraints, etc. checkAddNode(destParentState, destName, srcState.getNodeTypeName(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); // 3. verify that source has mixin mix:shareable if (!isShareable(srcState)) { String msg = "Cloning inside a workspace is only allowed for shareable" + " nodes. Node with type " + srcState.getNodeTypeName() + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // 4. detect share cycle NodeId srcId = srcState.getNodeId(); NodeId destParentId = destParentState.getNodeId(); if (destParentId.equals(srcId) || hierMgr.isAncestor(srcId, destParentId)) { String msg = "Cloning Node with id " + srcId + " to parent with id " + destParentId + " would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // 5. do clone operation (modify and store affected states) if (!srcState.addShare(destParentState.getNodeId())) { String msg = "Adding a shareable node with id (" + destParentState.getNodeId() + ") twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } destParentState.addChildNodeEntry(destName, srcState.getNodeId()); // store states stateMgr.store(srcState); stateMgr.store(destParentState); return destParentState.getNodeId(); } /** * Copies the tree at srcPath to the new location at * destPath. Returns the id of the node at its new position. * * Precondition: the state manager needs to be in edit mode. * * @param srcPath * @param destPath * @param flag one of * * COPY * CLONE * CLONE_REMOVE_EXISTING * * @return the id of the node at its new position * @throws RepositoryException if the copy operation fails */ public NodeId copy(Path srcPath, Path destPath, int flag) throws RepositoryException { return copy( srcPath, stateMgr, hierMgr, context.getAccessManager(), destPath, flag); } /** * Copies the tree at srcPath retrieved using the specified * srcStateMgr to the new location at destPath. * Returns the id of the node at its new position. * * Precondition: the state manager needs to be in edit mode. * * @param srcPath * @param srcStateMgr * @param srcHierMgr * @param srcAccessMgr * @param destPath * @param flag one of * * COPY * CLONE * CLONE_REMOVE_EXISTING * * @return the id of the node at its new position * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws PathNotFoundException * @throws ItemExistsException * @throws LockException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode. */ public NodeId copy(Path srcPath, ItemStateManager srcStateMgr, HierarchyManager srcHierMgr, AccessManager srcAccessMgr, Path destPath, int flag) throws ConstraintViolationException, AccessDeniedException, VersionException, PathNotFoundException, ItemExistsException, LockException, RepositoryException, IllegalStateException { // check precondition checkInEditMode(); // 1. check paths & retrieve state NodeState srcState = getNodeState(srcStateMgr, srcHierMgr, srcPath); Path destParentPath = destPath.getAncestor(1); NodeState destParentState = getNodeState(destParentPath); int ind = destPath.getIndex(); if (ind > 0) { // subscript in name element String msg = "invalid copy destination path: " + safeGetJCRPath(destPath) + " (subscript in name element is not allowed)"; log.debug(msg); throw new RepositoryException(msg); } // 2. check access rights, lock status, node type constraints, etc. // JCR-2269: store target node state in changelog early as a // precautionary measure in order to isolate it from concurrent // underlying changes while checking preconditions stateMgr.store(destParentState); checkAddNode(destParentState, destPath.getName(), srcState.getNodeTypeName(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); // check read access right on source node using source access manager try { if (!srcAccessMgr.isGranted(srcPath, Permission.READ)) { throw new PathNotFoundException(safeGetJCRPath(srcPath)); } } catch (ItemNotFoundException infe) { String msg = "internal error: failed to check access rights for " + safeGetJCRPath(srcPath); log.debug(msg); throw new RepositoryException(msg, infe); } // 3. do copy operation (modify and store affected states) ReferenceChangeTracker refTracker = new ReferenceChangeTracker(); // create deep copy of source node state NodeState newState = copyNodeState(srcState, srcPath, srcStateMgr, srcAccessMgr, destParentState.getNodeId(), flag, refTracker); // add to new parent destParentState.addChildNodeEntry(destPath.getName(), newState.getNodeId()); // adjust references that refer to uuid's which have been mapped to // newly generated uuid's on copy/clone Iterator iter = refTracker.getProcessedReferences(); while (iter.hasNext()) { PropertyState prop = (PropertyState) iter.next(); // being paranoid... if (prop.getType() != PropertyType.REFERENCE && prop.getType() != PropertyType.WEAKREFERENCE) { continue; } boolean modified = false; InternalValue[] values = prop.getValues(); InternalValue[] newVals = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { NodeId adjusted = refTracker.getMappedId(values[i].getNodeId()); if (adjusted != null) { boolean weak = prop.getType() == PropertyType.WEAKREFERENCE; newVals[i] = InternalValue.create(adjusted, weak); modified = true; } else { // reference doesn't need adjusting, just copy old value newVals[i] = values[i]; } } if (modified) { prop.setValues(newVals); stateMgr.store(prop); } } refTracker.clear(); // store states stateMgr.store(newState); stateMgr.store(destParentState); return newState.getNodeId(); } /** * Moves the tree at srcPath to the new location at * destPath. Returns the id of the moved node. * * Precondition: the state manager needs to be in edit mode. * * @param srcPath * @param destPath * @return the id of the moved node * @throws ConstraintViolationException * @throws VersionException * @throws AccessDeniedException * @throws PathNotFoundException * @throws ItemExistsException * @throws LockException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode */ public NodeId move(Path srcPath, Path destPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot move path " + safeGetJCRPath(srcPath) + " because manager is not in edit mode"); } // 1. check paths & retrieve state try { if (srcPath.isAncestorOf(destPath)) { String msg = safeGetJCRPath(destPath) + ": invalid destination path" + " (cannot be descendant of source path)"; log.debug(msg); throw new RepositoryException(msg); } } catch (MalformedPathException mpe) { String msg = "invalid path for move: " + safeGetJCRPath(destPath); log.debug(msg); throw new RepositoryException(msg, mpe); } Path srcParentPath = srcPath.getAncestor(1); NodeState target = getNodeState(srcPath); NodeState srcParent = getNodeState(srcParentPath); Path destParentPath = destPath.getAncestor(1); NodeState destParent = getNodeState(destParentPath); int ind = destPath.getIndex(); if (ind > 0) { // subscript in name element String msg = safeGetJCRPath(destPath) + ": invalid destination path" + " (subscript in name element is not allowed)"; log.debug(msg); throw new RepositoryException(msg); } HierarchyManagerImpl hierMgr = (HierarchyManagerImpl) this.hierMgr; if (hierMgr.isShareAncestor(target.getNodeId(), destParent.getNodeId())) { String msg = safeGetJCRPath(destPath) + ": invalid destination path" + " (share cycle detected)"; log.debug(msg); throw new RepositoryException(msg); } // 2. check if target state can be removed from old/added to new parent checkRemoveNode(target, srcParent.getNodeId(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); checkAddNode(destParent, destPath.getName(), target.getNodeTypeName(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); // 3. do move operation (modify and store affected states) boolean renameOnly = srcParent.getNodeId().equals(destParent.getNodeId()); int srcNameIndex = srcPath.getIndex(); if (srcNameIndex == 0) { srcNameIndex = 1; } stateMgr.store(target); if (renameOnly) { stateMgr.store(srcParent); // change child node entry destParent.renameChildNodeEntry(srcPath.getName(), srcNameIndex, destPath.getName()); } else { // check shareable case if (target.isShareable()) { String msg = "Moving a shareable node (" + safeGetJCRPath(srcPath) + ") is not supported."; log.debug(msg); 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())) { // 2. re-parent target node target.setParentId(destParent.getNodeId()); // 3. add child node entry to new parent destParent.addChildNodeEntry(destPath.getName(), target.getNodeId()); } } return target.getNodeId(); } /** * Removes the specified node, recursively removing its properties and * child nodes. * * Precondition: the state manager needs to be in edit mode. * * @param nodePath * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws LockException * @throws ItemNotFoundException * @throws ReferentialIntegrityException * @throws RepositoryException * @throws IllegalStateException */ public void removeNode(Path nodePath) throws ConstraintViolationException, AccessDeniedException, VersionException, LockException, ItemNotFoundException, ReferentialIntegrityException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot remove node (" + safeGetJCRPath(nodePath) + ") because manager is not in edit mode"); } // 1. retrieve affected state NodeState target = getNodeState(nodePath); NodeId parentId = target.getParentId(); // 2. check if target state can be removed from parent checkRemoveNode(target, parentId, CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_REFERENCES | CHECK_HOLD | CHECK_RETENTION); // 3. do remove operation removeNodeState(target); } //--------------------------------------< misc. high-level helper methods > /** * Checks if adding a child node called nodeName of node type * nodeTypeName to the given parent node is allowed in the * current context. * * @param parentState * @param nodeName * @param nodeTypeName * @param options bit-wise OR'ed flags specifying the checks that should be * performed; any combination of the following constants: * * {@link #CHECK_ACCESS}: make sure * current session is granted read & write access on * parent node * {@link #CHECK_LOCK}: make sure * there's no foreign lock on parent node * {@link #CHECK_CHECKED_OUT}: make sure * parent node is checked-out * {@link #CHECK_CONSTRAINTS}: * make sure no node type constraints would be violated * {@link #CHECK_HOLD}: check for effective holds preventing the add operation * {@link #CHECK_RETENTION}: check for effective retention policy preventing the add operation * * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws LockException * @throws ItemNotFoundException * @throws ItemExistsException * @throws RepositoryException */ public void checkAddNode(NodeState parentState, Name nodeName, Name nodeTypeName, int options) throws ConstraintViolationException, AccessDeniedException, VersionException, LockException, ItemNotFoundException, ItemExistsException, RepositoryException { Path parentPath = hierMgr.getPath(parentState.getNodeId()); // 1. locking status if ((options & CHECK_LOCK) == CHECK_LOCK) { // make sure there's no foreign lock on parent node verifyUnlocked(parentPath); } // 2. versioning status if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { // make sure parent node is checked-out verifyCheckedOut(parentPath); } // 3. access rights if ((options & CHECK_ACCESS) == CHECK_ACCESS) { AccessManager accessMgr = context.getAccessManager(); // make sure current session is granted read access on parent node if (!accessMgr.isGranted(parentPath, Permission.READ)) { throw new ItemNotFoundException(safeGetJCRPath(parentState.getNodeId())); } // make sure current session is granted write access on parent node if (!accessMgr.isGranted(parentPath, nodeName, Permission.ADD_NODE)) { throw new AccessDeniedException(safeGetJCRPath(parentState.getNodeId()) + ": not allowed to add child node"); } // make sure the editing session is allowed create nodes with a // specified node type (and ev. mixins) if (!accessMgr.isGranted(parentPath, nodeName, Permission.NODE_TYPE_MNGMT)) { throw new AccessDeniedException(safeGetJCRPath(parentState.getNodeId()) + ": not allowed to add child node"); } } // 4. node type constraints if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { QItemDefinition parentDef = context.getItemManager().getDefinition(parentState).unwrap(); // make sure parent node is not protected if (parentDef.isProtected()) { throw new ConstraintViolationException( safeGetJCRPath(parentState.getNodeId()) + ": cannot add child node to protected parent node"); } // make sure there's an applicable definition for new child node EffectiveNodeType entParent = getEffectiveNodeType(parentState); entParent.checkAddNodeConstraints( nodeName, nodeTypeName, context.getNodeTypeRegistry()); QNodeDefinition newNodeDef = findApplicableNodeDefinition(nodeName, nodeTypeName, parentState); // check for name collisions if (parentState.hasChildNodeEntry(nodeName)) { // there's already a node with that name... // get definition of existing conflicting node ChildNodeEntry entry = parentState.getChildNodeEntry(nodeName, 1); NodeState conflictingState; NodeId conflictingId = entry.getId(); try { conflictingState = (NodeState) stateMgr.getItemState(conflictingId); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + safeGetJCRPath(conflictingId); log.debug(msg); throw new RepositoryException(msg, ise); } QNodeDefinition conflictingTargetDef = context.getItemManager().getDefinition(conflictingState).unwrap(); // check same-name sibling setting of both target and existing node if (!conflictingTargetDef.allowsSameNameSiblings() || !newNodeDef.allowsSameNameSiblings()) { throw new ItemExistsException( "cannot add child node '" + nodeName.getLocalName() + "' to " + safeGetJCRPath(parentState.getNodeId()) + ": colliding with same-named existing node"); } } } RetentionRegistry retentionReg = context.getSessionImpl().getRetentionRegistry(); if ((options & CHECK_HOLD) == CHECK_HOLD) { if (retentionReg.hasEffectiveHold(parentPath, false)) { throw new RepositoryException("Unable to add node. Parent is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (retentionReg.hasEffectiveRetention(parentPath, false)) { throw new RepositoryException("Unable to add node. Parent is affected by a retention."); } } } /** * Checks if removing the given target node is allowed in the current context. * * @param targetState * @param options bit-wise OR'ed flags specifying the checks that should be * performed; any combination of the following constants: * * {@link #CHECK_ACCESS}: make sure * current session is granted read access on parent * and remove privilege on target node * {@link #CHECK_LOCK}: make sure * there's no foreign lock on parent node * {@link #CHECK_CHECKED_OUT}: make sure * parent node is checked-out * {@link #CHECK_CONSTRAINTS}: * make sure no node type constraints would be violated * {@link #CHECK_REFERENCES}: * make sure no references exist on target node * {@link #CHECK_HOLD}: check for effective holds preventing the add operation * {@link #CHECK_RETENTION}: check for effective retention policy preventing the add operation * * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws LockException * @throws ItemNotFoundException * @throws ReferentialIntegrityException * @throws RepositoryException */ public void checkRemoveNode(NodeState targetState, int options) throws ConstraintViolationException, AccessDeniedException, VersionException, LockException, ItemNotFoundException, ReferentialIntegrityException, RepositoryException { checkRemoveNode(targetState, targetState.getParentId(), options); } /** * Checks if removing the given target node from the specifed parent * is allowed in the current context. * * @param targetState * @param parentId * @param options bit-wise OR'ed flags specifying the checks that should be * performed; any combination of the following constants: * * {@link #CHECK_ACCESS}: make sure * current session is granted read access on parent * and remove privilege on target node * {@link #CHECK_LOCK}: make sure * there's no foreign lock on parent node * {@link #CHECK_CHECKED_OUT}: make sure * parent node is checked-out * {@link #CHECK_CONSTRAINTS}: * make sure no node type constraints would be violated * {@link #CHECK_REFERENCES}: * make sure no references exist on target node * {@link #CHECK_HOLD}: check for effective holds preventing the add operation * {@link #CHECK_RETENTION}: check for effective retention policy preventing the add operation * * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws LockException * @throws ItemNotFoundException * @throws ReferentialIntegrityException * @throws RepositoryException */ public void checkRemoveNode(NodeState targetState, NodeId parentId, int options) throws ConstraintViolationException, AccessDeniedException, VersionException, LockException, ItemNotFoundException, ReferentialIntegrityException, RepositoryException { if (targetState.getParentId() == null) { // root or orphaned node throw new ConstraintViolationException("cannot remove root node"); } Path targetPath = hierMgr.getPath(targetState.getNodeId()); NodeState parentState = getNodeState(parentId); Path parentPath = hierMgr.getPath(parentId); // 1. locking status if ((options & CHECK_LOCK) == CHECK_LOCK) { // make sure there's no foreign lock on parent node verifyUnlocked(parentPath); } // 2. versioning status if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { // make sure parent node is checked-out verifyCheckedOut(parentPath); } // 3. access rights if ((options & CHECK_ACCESS) == CHECK_ACCESS) { try { AccessManager accessMgr = context.getAccessManager(); // make sure current session is granted read access on parent node if (!accessMgr.isGranted(targetPath, Permission.READ)) { throw new PathNotFoundException(safeGetJCRPath(targetPath)); } // make sure current session is allowed to remove target node if (!accessMgr.isGranted(targetPath, Permission.REMOVE_NODE)) { throw new AccessDeniedException(safeGetJCRPath(targetPath) + ": not allowed to remove node"); } } catch (ItemNotFoundException infe) { String msg = "internal error: failed to check access rights for " + safeGetJCRPath(targetPath); log.debug(msg); throw new RepositoryException(msg, infe); } } // 4. node type constraints if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { QItemDefinition parentDef = context.getItemManager().getDefinition(parentState).unwrap(); if (parentDef.isProtected()) { throw new ConstraintViolationException(safeGetJCRPath(parentId) + ": cannot remove child node of protected parent node"); } QItemDefinition targetDef = context.getItemManager().getDefinition(targetState).unwrap(); if (targetDef.isMandatory()) { throw new ConstraintViolationException(safeGetJCRPath(targetPath) + ": cannot remove mandatory node"); } if (targetDef.isProtected()) { throw new ConstraintViolationException(safeGetJCRPath(targetPath) + ": cannot remove protected node"); } } // 5. referential integrity if ((options & CHECK_REFERENCES) == CHECK_REFERENCES) { EffectiveNodeType ent = getEffectiveNodeType(targetState); if (ent.includesNodeType(NameConstants.MIX_REFERENCEABLE)) { NodeId targetId = targetState.getNodeId(); if (stateMgr.hasNodeReferences(targetId)) { try { NodeReferences refs = stateMgr.getNodeReferences(targetId); if (refs.hasReferences()) { throw new ReferentialIntegrityException(safeGetJCRPath(targetPath) + ": cannot remove node with references"); } } catch (ItemStateException ise) { String msg = "internal error: failed to check references on " + safeGetJCRPath(targetPath); log.error(msg, ise); throw new RepositoryException(msg, ise); } } } } RetentionRegistry retentionReg = context.getSessionImpl().getRetentionRegistry(); if ((options & CHECK_HOLD) == CHECK_HOLD) { if (retentionReg.hasEffectiveHold(targetPath, true)) { throw new RepositoryException("Unable to perform removal. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (retentionReg.hasEffectiveRetention(targetPath, true)) { throw new RepositoryException("Unable to perform removal. Node is affected by a retention."); } } } /** * Verifies that the node at nodePath is writable. The * following conditions must hold true: * * the node must exist * the current session must be granted read & write access on it * the node must not be locked by another session * the node must not be checked-in * the node must not be protected * the node must not be affected by a hold or a retention policy * * * @param nodePath path of node to check * @throws PathNotFoundException if no node exists at * nodePath of the current * session is not granted read access * to the specified path * @throws AccessDeniedException if write access to the specified * path is not allowed * @throws ConstraintViolationException if the node at nodePath * is protected * @throws VersionException if the node at nodePath * is checked-in * @throws LockException if the node at nodePath * is locked by another session * @throws RepositoryException if another error occurs */ public void verifyCanWrite(Path nodePath) throws PathNotFoundException, AccessDeniedException, ConstraintViolationException, VersionException, LockException, RepositoryException { NodeState node = getNodeState(nodePath); // access rights // make sure current session is granted read access on node AccessManager accessMgr = context.getAccessManager(); if (!accessMgr.isGranted(nodePath, Permission.READ)) { throw new PathNotFoundException(safeGetJCRPath(node.getNodeId())); } // TODO: removed check for 'WRITE' permission on node due to the fact, // TODO: that add_node and set_property permission are granted on the // TODO: items to be create/modified and not on their parent. // in any case, the ability to add child-nodes and properties is checked // while executing the corresponding operation. // locking status verifyUnlocked(nodePath); // node type constraints verifyNotProtected(nodePath); // versioning status verifyCheckedOut(nodePath); RetentionRegistry retentionReg = context.getSessionImpl().getRetentionRegistry(); if (retentionReg.hasEffectiveHold(nodePath, false)) { throw new RepositoryException("Unable to write. Node is affected by a hold."); } if (retentionReg.hasEffectiveRetention(nodePath, false)) { throw new RepositoryException("Unable to write. Node is affected by a retention."); } } /** * Verifies that the node at nodePath can be read. The * following conditions must hold true: * * the node must exist * the current session must be granted read access on it * * * @param nodePath path of node to check * @throws PathNotFoundException if no node exists at * nodePath of the current * session is not granted read access * to the specified path * @throws RepositoryException if another error occurs */ public void verifyCanRead(Path nodePath) throws PathNotFoundException, RepositoryException { // access rights // make sure current session is granted read access on node AccessManager accessMgr = context.getAccessManager(); if (!accessMgr.isGranted(nodePath, Permission.READ)) { throw new PathNotFoundException(safeGetJCRPath(nodePath)); } } //--------------------------------------------< low-level item operations > /** * Creates a new node. * * Note that access rights are not enforced! * * Precondition: the state manager needs to be in edit mode. * * @param parent * @param nodeName * @param nodeTypeName * @param mixinNames * @param id * @return * @throws ItemExistsException * @throws ConstraintViolationException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode. */ public NodeState createNodeState(NodeState parent, Name nodeName, Name nodeTypeName, Name[] mixinNames, NodeId id) throws ItemExistsException, ConstraintViolationException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot create node state for " + nodeName + " because manager is not in edit mode"); } QNodeDefinition def = findApplicableNodeDefinition(nodeName, nodeTypeName, parent); return createNodeState(parent, nodeName, nodeTypeName, mixinNames, id, def); } /** * Creates a new node based on the given definition. * * Note that access rights are not enforced! * * Precondition: the state manager needs to be in edit mode. * * @param parent * @param nodeName * @param nodeTypeName * @param mixinNames * @param id * @param def * @return * @throws ItemExistsException * @throws ConstraintViolationException * @throws RepositoryException * @throws IllegalStateException */ public NodeState createNodeState(NodeState parent, Name nodeName, Name nodeTypeName, Name[] mixinNames, NodeId id, QNodeDefinition def) throws ItemExistsException, ConstraintViolationException, RepositoryException, IllegalStateException { // check for name collisions with existing nodes if (!def.allowsSameNameSiblings() && parent.hasChildNodeEntry(nodeName)) { NodeId errorId = parent.getChildNodeEntry(nodeName, 1).getId(); throw new ItemExistsException(safeGetJCRPath(errorId)); } if (nodeTypeName == null) { // no primary node type specified, // try default primary type from definition nodeTypeName = def.getDefaultPrimaryType(); if (nodeTypeName == null) { String msg = "an applicable node type could not be determined for " + nodeName; log.debug(msg); throw new ConstraintViolationException(msg); } } NodeState node = stateMgr.createNew(id, nodeTypeName, parent.getNodeId()); if (mixinNames != null && mixinNames.length > 0) { node.setMixinTypeNames(new HashSet(Arrays.asList(mixinNames))); } // now add new child node entry to parent parent.addChildNodeEntry(nodeName, node.getNodeId()); EffectiveNodeType ent = getEffectiveNodeType(node); // check shareable if (ent.includesNodeType(NameConstants.MIX_SHAREABLE)) { node.addShare(parent.getNodeId()); } if (!node.getMixinTypeNames().isEmpty()) { // create jcr:mixinTypes property QPropertyDefinition pd = ent.getApplicablePropertyDef(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true); createPropertyState(node, pd.getName(), pd.getRequiredType(), pd); } // add 'auto-create' properties defined in node type for (QPropertyDefinition pd : ent.getAutoCreatePropDefs()) { createPropertyState(node, pd.getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (QNodeDefinition nd : ent.getAutoCreateNodeDefs()) { createNodeState(node, nd.getName(), nd.getDefaultPrimaryType(), null, null, nd); } // store node stateMgr.store(node); // store parent stateMgr.store(parent); return node; } /** * Creates a new property. * * Note that access rights are not enforced! * * Precondition: the state manager needs to be in edit mode. * * @param parent * @param propName * @param type * @param numValues * @return * @throws ItemExistsException * @throws ConstraintViolationException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode */ public PropertyState createPropertyState(NodeState parent, Name propName, int type, int numValues) throws ItemExistsException, ConstraintViolationException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot create property state for " + propName + " because manager is not in edit mode"); } // find applicable definition QPropertyDefinition def; // multi- or single-valued property? if (numValues == 1) { // could be single- or multi-valued (n == 1) try { // try single-valued def = findApplicablePropertyDefinition(propName, type, false, parent); } catch (ConstraintViolationException cve) { // try multi-valued def = findApplicablePropertyDefinition(propName, type, true, parent); } } else { // can only be multi-valued (n == 0 || n > 1) def = findApplicablePropertyDefinition(propName, type, true, parent); } return createPropertyState(parent, propName, type, def); } /** * Creates a new property based on the given definition. * * Note that access rights are not enforced! * * Precondition: the state manager needs to be in edit mode. * * @param parent * @param propName * @param type * @param def * @return * @throws ItemExistsException * @throws RepositoryException */ public PropertyState createPropertyState(NodeState parent, Name propName, int type, QPropertyDefinition def) throws ItemExistsException, RepositoryException { // check for name collisions with existing properties if (parent.hasPropertyName(propName)) { PropertyId errorId = new PropertyId(parent.getNodeId(), propName); throw new ItemExistsException(safeGetJCRPath(errorId)); } // create property PropertyState prop = stateMgr.createNew(propName, parent.getNodeId()); if (def.getRequiredType() != PropertyType.UNDEFINED) { prop.setType(def.getRequiredType()); } else if (type != PropertyType.UNDEFINED) { prop.setType(type); } else { prop.setType(PropertyType.STRING); } prop.setMultiValued(def.isMultiple()); // compute system generated values if necessary new NodeTypeInstanceHandler(session.getUserID()).setDefaultValues( prop, parent, def); // now add new property entry to parent parent.addPropertyName(propName); // store parent stateMgr.store(parent); return prop; } /** * Unlinks the specified node state from its parent and recursively * removes it including its properties and child nodes. * * 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 * subject to the following checks: access rights, locking, versioning. * * @param target * @throws RepositoryException if an error occurs */ public void removeNodeState(NodeState target) throws RepositoryException { NodeId parentId = target.getParentId(); if (parentId == null) { String msg = "root node cannot be removed"; log.debug(msg); throw new RepositoryException(msg); } // remove target recursiveRemoveNodeState(target); // remove child node entry from parent NodeState parent = getNodeState(parentId); parent.removeChildNodeEntry(target.getNodeId()); // store parent stateMgr.store(parent); } /** * Retrieves the state of the node at the given path. * * Note that access rights are not enforced! * * @param nodePath * @return * @throws PathNotFoundException * @throws RepositoryException */ public NodeState getNodeState(Path nodePath) throws PathNotFoundException, RepositoryException { return getNodeState(stateMgr, hierMgr, nodePath); } /** * Retrieves the state of the node with the given id. * * Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public NodeState getNodeState(NodeId id) throws ItemNotFoundException, RepositoryException { return (NodeState) getItemState(stateMgr, id); } /** * Retrieves the state of the property with the given id. * * Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public PropertyState getPropertyState(PropertyId id) throws ItemNotFoundException, RepositoryException { return (PropertyState) getItemState(stateMgr, id); } /** * Retrieves the state of the item with the given id. * * Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public ItemState getItemState(ItemId id) throws ItemNotFoundException, RepositoryException { return getItemState(stateMgr, id); } //----------------------------------------------------< protected methods > /** * Verifies that the node at nodePath is checked-out; throws a * VersionException if that's not the case. * * A node is considered checked-out 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 * ancestors. * * @param nodePath * @throws PathNotFoundException * @throws VersionException * @throws RepositoryException */ protected void verifyCheckedOut(Path nodePath) throws PathNotFoundException, VersionException, RepositoryException { // search nearest ancestor that is versionable, start with node at nodePath /** * FIXME should not only rely on existence of jcr:isCheckedOut property * but also verify that node.isNodeType("mix:versionable")==true; * this would have a negative impact on performance though... */ NodeState nodeState = getNodeState(nodePath); while (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { if (nodePath.denotesRoot()) { return; } nodePath = nodePath.getAncestor(1); nodeState = getNodeState(nodePath); } PropertyId propId = new PropertyId(nodeState.getNodeId(), NameConstants.JCR_ISCHECKEDOUT); PropertyState propState; try { propState = (PropertyState) stateMgr.getItemState(propId); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + safeGetJCRPath(propId); log.debug(msg); throw new RepositoryException(msg, ise); } boolean checkedOut = propState.getValues()[0].getBoolean(); if (!checkedOut) { throw new VersionException(safeGetJCRPath(nodePath) + " is checked-in"); } } /** * Verifies that the node at nodePath is not locked by * somebody else than the current session. * * @param nodePath path of node to check * @throws PathNotFoundException * @throws LockException if write access to the specified path is not allowed * @throws RepositoryException if another error occurs */ protected void verifyUnlocked(Path nodePath) throws LockException, RepositoryException { // make sure there's no foreign lock on node at nodePath context.getWorkspace().getInternalLockManager().checkLock( nodePath, session); } /** * Verifies that the node at nodePath is not protected. * * @param nodePath path of node to check * @throws PathNotFoundException if no node exists at nodePath * @throws ConstraintViolationException if write access to the specified * path is not allowed * @throws RepositoryException if another error occurs */ protected void verifyNotProtected(Path nodePath) throws PathNotFoundException, ConstraintViolationException, RepositoryException { NodeState node = getNodeState(nodePath); if (context.getItemManager().getDefinition(node).isProtected()) { throw new ConstraintViolationException(safeGetJCRPath(nodePath) + ": node is protected"); } } /** * Retrieves the state of the node at nodePath using the given * item state manager. * * Note that access rights are not enforced! * * @param srcStateMgr * @param srcHierMgr * @param nodePath * @return * @throws PathNotFoundException * @throws RepositoryException */ protected NodeState getNodeState(ItemStateManager srcStateMgr, HierarchyManager srcHierMgr, Path nodePath) throws PathNotFoundException, RepositoryException { try { NodeId id = srcHierMgr.resolveNodePath(nodePath); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(nodePath)); } return (NodeState) getItemState(srcStateMgr, id); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(nodePath)); } } /** * Retrieves the state of the item with the specified id using the given * item state manager. * * Note that access rights are not enforced! * * @param srcStateMgr * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ protected ItemState getItemState(ItemStateManager srcStateMgr, ItemId id) throws ItemNotFoundException, RepositoryException { try { return srcStateMgr.getItemState(id); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(safeGetJCRPath(id)); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + safeGetJCRPath(id); log.debug(msg); throw new RepositoryException(msg, ise); } } //------------------------------------------------------< private methods > /** * Recursively removes the given node state including its properties and * child nodes. * * The removal of child nodes is subject to the following checks: * access rights, locking & versioning status. Referential integrity * (references) is checked on commit. * * Note that the child node entry refering to targetState is * not automatically removed from targetState's * parent. * * @param targetState * @throws RepositoryException if an error occurs */ private void recursiveRemoveNodeState(NodeState targetState) throws RepositoryException { if (targetState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(targetState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); NodeId nodeId = entry.getId(); try { NodeState nodeState = (NodeState) stateMgr.getItemState(nodeId); // check if child node can be removed // (access rights, locking & versioning status as well // as retention and hold); // referential integrity (references) is checked // on commit checkRemoveNode(nodeState, targetState.getNodeId(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_HOLD | CHECK_RETENTION ); // remove child node recursiveRemoveNodeState(nodeState); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } // remove child node entry targetState.removeChildNodeEntry(entry.getName(), entry.getIndex()); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(targetState.getPropertyNames()); for (Name propName : tmp) { PropertyId propId = new PropertyId(targetState.getNodeId(), propName); try { PropertyState propState = (PropertyState) stateMgr.getItemState(propId); // remove property entry targetState.removePropertyName(propId.getName()); // destroy property state stateMgr.destroy(propState); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + propId; log.debug(msg); throw new RepositoryException(msg, ise); } } // now actually do unlink target state targetState.setParentId(null); // destroy target state (pass overlayed state since target state // might have been modified during unlinking) stateMgr.destroy(targetState.getOverlayedState()); } /** * Recursively copies the specified node state including its properties and * child nodes. * * @param srcState * @param srcPath * @param srcStateMgr * @param srcAccessMgr * @param destParentId * @param flag one of * * COPY * CLONE * CLONE_REMOVE_EXISTING * * @param refTracker tracks uuid mappings and processed reference properties * @return a deep copy of the given node state and its children * @throws RepositoryException if an error occurs */ private NodeState copyNodeState(NodeState srcState, Path srcPath, ItemStateManager srcStateMgr, AccessManager srcAccessMgr, NodeId destParentId, int flag, ReferenceChangeTracker refTracker) throws RepositoryException { NodeState newState; try { NodeId id = null; EffectiveNodeType ent = getEffectiveNodeType(srcState); boolean referenceable = ent.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean versionable = ent.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE); boolean fullVersionable = ent.includesNodeType(NameConstants.MIX_VERSIONABLE); boolean shareable = ent.includesNodeType(NameConstants.MIX_SHAREABLE); switch (flag) { case COPY: /* if this node is shareable and another node in the same shared set * has been already been copied and given a new uuid, use this one * (see section 14.5 of the specification) */ if (shareable && refTracker.getMappedId(srcState.getNodeId()) != null) { NodeId newId = refTracker.getMappedId(srcState.getNodeId()); NodeState sharedState = (NodeState) stateMgr.getItemState(newId); sharedState.addShare(destParentId); return sharedState; } break; case CLONE: if (!referenceable) { // non-referenceable node: always create new node id break; } // use same uuid as source node id = srcState.getNodeId(); if (stateMgr.hasItemState(id)) { if (shareable) { NodeState sharedState = (NodeState) stateMgr.getItemState(id); sharedState.addShare(destParentId); return sharedState; } // node with this uuid already exists throw new ItemExistsException(safeGetJCRPath(id)); } break; case CLONE_REMOVE_EXISTING: if (!referenceable) { // non-referenceable node: always create new node id break; } // use same uuid as source node id = srcState.getNodeId(); if (stateMgr.hasItemState(id)) { NodeState existingState = (NodeState) stateMgr.getItemState(id); // make sure existing node is not the parent // or an ancestor thereof if (id.equals(destParentId) || hierMgr.isAncestor(id, destParentId)) { String msg = "cannot remove node " + safeGetJCRPath(srcPath) + " because it is an ancestor of the destination"; log.debug(msg); throw new RepositoryException(msg); } // check if existing can be removed // (access rights, locking & versioning status, // node type constraints and retention/hold) checkRemoveNode(existingState, CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); // do remove existing removeNodeState(existingState); } break; default: throw new IllegalArgumentException( "unknown flag for copying node state: " + flag); } newState = stateMgr.createNew(id, srcState.getNodeTypeName(), destParentId); id = newState.getNodeId(); if (flag == COPY && referenceable) { // remember uuid mapping refTracker.mappedId(srcState.getNodeId(), id); } // copy node state newState.setMixinTypeNames(srcState.getMixinTypeNames()); if (shareable) { // initialize shared set newState.addShare(destParentId); } // copy child nodes for (ChildNodeEntry entry : srcState.getChildNodeEntries()) { Path srcChildPath = PathFactoryImpl.getInstance().create(srcPath, entry.getName(), true); if (!srcAccessMgr.isGranted(srcChildPath, Permission.READ)) { continue; } NodeId nodeId = entry.getId(); NodeState srcChildState = (NodeState) srcStateMgr.getItemState(nodeId); /** * special handling required for child nodes with special semantics * (e.g. those defined by nt:version, et.al.) * * todo FIXME delegate to 'node type instance handler' */ /** * If child is shareble and its UUID has already been remapped, * then simply add a reference to the state with that remapped * UUID instead of copying the whole subtree. */ if (srcChildState.isShareable()) { NodeId mappedId = refTracker.getMappedId(srcChildState.getNodeId()); if (mappedId != null) { if (stateMgr.hasItemState(mappedId)) { NodeState destState = (NodeState) stateMgr.getItemState(mappedId); if (!destState.isShareable()) { String msg = "Remapped child (" + safeGetJCRPath(srcPath) + ") is not shareable."; throw new ItemStateException(msg); } if (!destState.addShare(id)) { String msg = "Unable to add share to node: " + id; throw new ItemStateException(msg); } stateMgr.store(destState); newState.addChildNodeEntry(entry.getName(), mappedId); continue; } } } // recursive copying of child node NodeState newChildState = copyNodeState(srcChildState, srcChildPath, srcStateMgr, srcAccessMgr, id, flag, refTracker); // store new child node stateMgr.store(newChildState); // add new child node entry to new node newState.addChildNodeEntry(entry.getName(), newChildState.getNodeId()); } // init version history if needed VersionHistoryInfo history = null; if (versionable && flag == COPY) { NodeId copiedFrom = null; if (fullVersionable) { // base version of copied versionable node is reference value of // the histories jcr:copiedFrom property PropertyId propId = new PropertyId(srcState.getNodeId(), NameConstants.JCR_BASEVERSION); PropertyState prop = (PropertyState) srcStateMgr.getItemState(propId); copiedFrom = prop.getValues()[0].getNodeId(); } InternalVersionManager manager = session.getInternalVersionManager(); history = manager.getVersionHistory(session, newState, copiedFrom); } // copy properties for (Name propName : srcState.getPropertyNames()) { Path propPath = PathFactoryImpl.getInstance().create(srcPath, propName, true); PropertyId propId = new PropertyId(srcState.getNodeId(), propName); if (!srcAccessMgr.canRead(propPath, propId)) { continue; } PropertyState srcChildState = (PropertyState) srcStateMgr.getItemState(propId); /** * special handling required for properties with special semantics * (e.g. those defined by mix:referenceable, mix:versionable, * mix:lockable, et.al.) * * todo FIXME delegate to 'node type instance handler' */ QPropertyDefinition def = ent.getApplicablePropertyDef( srcChildState.getName(), srcChildState.getType(), srcChildState.isMultiValued()); if (NameConstants.MIX_LOCKABLE.equals(def.getDeclaringNodeType())) { // skip properties defined by mix:lockable continue; } PropertyState newChildState = copyPropertyState(srcChildState, id, propName, def); if (history != null) { if (fullVersionable) { if (propName.equals(NameConstants.JCR_VERSIONHISTORY)) { // jcr:versionHistory InternalValue value = InternalValue.create( history.getVersionHistoryId()); newChildState.setValues(new InternalValue[] { value }); } else if (propName.equals(NameConstants.JCR_BASEVERSION) || propName.equals(NameConstants.JCR_PREDECESSORS)) { // jcr:baseVersion or jcr:predecessors InternalValue value = InternalValue.create( history.getRootVersionId()); newChildState.setValues(new InternalValue[] { value }); } else if (propName.equals(NameConstants.JCR_ISCHECKEDOUT)) { // jcr:isCheckedOut newChildState.setValues(new InternalValue[]{InternalValue.create(true)}); } } else { // for simple versionable, we just initialize the // version history when we see the jcr:isCheckedOut if (propName.equals(NameConstants.JCR_ISCHECKEDOUT)) { // jcr:isCheckedOut newChildState.setValues(new InternalValue[]{InternalValue.create(true)}); } } } if (newChildState.getType() == PropertyType.REFERENCE || newChildState.getType() == PropertyType.WEAKREFERENCE) { refTracker.processedReference(newChildState); } // store new property stateMgr.store(newChildState); // add new property entry to new node newState.addPropertyName(propName); } return newState; } catch (ItemStateException ise) { String msg = "internal error: failed to copy state of " + srcState.getNodeId(); log.debug(msg); throw new RepositoryException(msg, ise); } } /** * Copies the specified property state. * * @param srcState the property state to copy. * @param parentId the id of the parent node. * @param propName the name of the property. * @param def the definition of the property. * @return a copy of the property state. * @throws RepositoryException if an error occurs while copying. */ private PropertyState copyPropertyState(PropertyState srcState, NodeId parentId, Name propName, QPropertyDefinition def) throws RepositoryException { PropertyState newState = stateMgr.createNew(propName, parentId); newState.setType(srcState.getType()); newState.setMultiValued(srcState.isMultiValued()); InternalValue[] values = srcState.getValues(); if (values != null) { /** * special handling required for properties with special semantics * (e.g. those defined by mix:referenceable, mix:versionable, * mix:lockable, et.al.) * * todo FIXME delegate to 'node type instance handler' */ if (propName.equals(NameConstants.JCR_UUID) && def.getDeclaringNodeType().equals(NameConstants.MIX_REFERENCEABLE)) { // set correct value of jcr:uuid property newState.setValues(new InternalValue[]{InternalValue.create(parentId.toString())}); } else { InternalValue[] newValues = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { newValues[i] = values[i].createCopy(); } newState.setValues(newValues); } } return newState; } /** * Check that the updatable item state manager is in edit mode. * * @throws IllegalStateException if it isn't */ private void checkInEditMode() throws IllegalStateException { if (!stateMgr.inEditMode()) { throw new IllegalStateException("not in edit mode"); } } /** * Determines whether the specified node is shareable, i.e. * whether the mixin type mix:shareable is either * directly assigned or indirectly inherited. * * @param state node state to check * @return true if the specified node is shareable, false otherwise. * @throws RepositoryException if an error occurs */ private boolean isShareable(NodeState state) throws RepositoryException { // shortcut: check some wellknown built-in types first Name primary = state.getNodeTypeName(); Set mixins = state.getMixinTypeNames(); if (mixins.contains(NameConstants.MIX_SHAREABLE)) { return true; } try { NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(NameConstants.MIX_REFERENCEABLE); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + state.getNodeId(); log.debug(msg); throw new RepositoryException(msg, ntce); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.jcr.ItemNotFoundException; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.NodeStateListener; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.name.PathMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of a HierarchyManager that caches paths of * items. */ public class CachingHierarchyManager extends HierarchyManagerImpl implements NodeStateListener { /** * Default upper limit of cached states */ public static final int DEFAULT_UPPER_LIMIT = 10000; private static final int MAX_UPPER_LIMIT = Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.cacheSize", DEFAULT_UPPER_LIMIT); private static final int CACHE_STATISTICS_LOG_INTERVAL_MILLIS = Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.logInterval", 60000); /** * Logger instance */ private static Logger log = LoggerFactory.getLogger(CachingHierarchyManager.class); /** * Mapping of paths to children in the path map */ private final PathMap pathCache = new PathMap(); /** * Mapping of item ids to LRUEntry in the path map */ private final ReferenceMap idCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); /** * Cache monitor object */ private final Object cacheMonitor = new Object(); /** * Upper limit */ private final int upperLimit; /** * Object collecting and logging statistics about the idCache */ private final CacheStatistics idCacheStatistics; /** * Head of LRU */ private LRUEntry head; /** * Tail of LRU */ private LRUEntry tail; /** * Flag indicating whether consistency checking is enabled. */ 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 * @param provider item state manager */ public CachingHierarchyManager(NodeId rootNodeId, ItemStateManager provider) { super(rootNodeId, provider); upperLimit = MAX_UPPER_LIMIT; idCacheStatistics = new CacheStatistics(); if (log.isTraceEnabled()) { log.trace("CachingHierarchyManager initialized. Max cache size = {}", upperLimit, new Exception()); } else { log.debug("CachingHierarchyManager initialized. Max cache size = {}", upperLimit); } } /** * Enable or disable consistency checks in this instance. * * @param enable true to enable consistency checks; * false to disable */ public void enableConsistencyChecks(boolean enable) { this.consistencyCheckEnabled = enable; } //-------------------------------------------------< base class overrides > /** * {@inheritDoc} */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path pathToNode = path; if ((typesAllowed & RETURN_NODE) == 0) { // if we must not return a node, pass parent path // (since we only cache nodes) pathToNode = path.getAncestor(1); } PathMap.Element element = map(pathToNode); if (element == null) { // not even intermediate match: call base class return super.resolvePath(path, typesAllowed); } LRUEntry entry = element.get(); if (element.hasPath(path)) { // exact match: return answer synchronized (cacheMonitor) { entry.touch(); } return entry.getId(); } Path.Element[] elements = path.getElements(); try { return resolvePath(elements, element.getDepth() + 1, entry.getId(), typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node for entry: " + entry.getId() + ", path: " + path.getString(); logItemStateException(msg, e); log.debug(msg); // 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); } /** * {@inheritDoc} */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { if (id.denotesNode()) { cache((NodeId) id, builder.getPath()); } } /** * {@inheritDoc} * * Overridden method tries to find a mapping for the intermediate item * state 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, CycleDetector detector) throws ItemStateException, RepositoryException { if (state.isNode()) { PathMap.Element element = get(state.getId()); if (element != null) { try { Path.Element[] elements = element.getPath().getElements(); for (int i = elements.length - 1; i >= 0; i--) { builder.addFirst(elements[i]); } return; } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + state.getId(); log.debug(msg); throw new RepositoryException(msg, mpe); } } } super.buildPath(builder, state, detector); if (state.isNode()) { try { cache(((NodeState) state).getNodeId(), builder.getPath()); } catch (MalformedPathException mpe) { log.warn("Failed to build path of " + state.getId()); } } } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} * * Overridden method simply checks whether we have an item matching the id * and returns its path, otherwise calls base implementation. */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { try { return element.getPath(); } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } } return super.getPath(id); } /** * {@inheritDoc} */ public Name getName(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getName(); } } return super.getName(id); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getDepth(); } } return super.getDepth(id); } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { PathMap.Element element = get(nodeId); if (element != null) { PathMap.Element child = get(itemId); if (child != null) { return element.isAncestorOf(child); } } } return super.isAncestor(nodeId, itemId); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { if (modified.isNode()) { nodeModified((NodeState) modified); } } /** * {@inheritDoc} * * If path information is cached for modified, this iterates * over all child nodes in the path map, evicting the ones that do not * (longer) exist in the underlying NodeState. */ public void nodeModified(NodeState modified) { synchronized (cacheMonitor) { for (PathMap.Element element : getCachedPaths(modified.getNodeId())) { for (PathMap.Element child : element.getChildren()) { ChildNodeEntry cne = modified.getChildNodeEntry( child.getName(), child.getNormalizedIndex()); if (cne == null) { // Item does not exist, remove evict(child, true); } else { LRUEntry childEntry = child.get(); if (childEntry != null && !cne.getId().equals(childEntry.getId())) { // Different child item, remove evict(child, true); } } } } checkConsistency(); } } private List> getCachedPaths(NodeId id) { // JCR-2720: Handle the root path as a special case if (rootNodeId.equals(id)) { return Collections.singletonList(pathCache.map( PathFactoryImpl.getInstance().getRootPath(), true)); } LRUEntry entry = idCache.get(id); if (entry != null) { return Arrays.asList(entry.getElements()); } else { return Collections.emptyList(); } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { evictAll(destroyed.getId(), true); } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { if (discarded.isTransient() && !discarded.hasOverlayedState() && discarded.getStatus() == ItemState.STATUS_NEW) { // a new node has been discarded -> remove from cache evictAll(discarded.getId(), true); } else if (provider.hasItemState(discarded.getId())) { evictAll(discarded.getId(), false); } else { evictAll(discarded.getId(), true); } } /** * {@inheritDoc} */ public void nodeAdded(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to find item " + state.getNodeId(), e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } 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} * * Iterate over all cached children of this state and verify each * child's position. */ public void nodesReplaced(NodeState state) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(state.getNodeId()); if (entry == null) { return; } for (PathMap.Element parent : entry.getElements()) { HashMap> newChildrenOrder = new HashMap>(); boolean orderChanged = false; for (PathMap.Element child : parent.getChildren()) { LRUEntry childEntry = child.get(); if (childEntry == null) { // Child has no associated UUID information: we're // therefore unable to determine if this child's // position is still accurate and have to assume // the worst and remove it. evict(child, false); } else { NodeId childId = childEntry.getId(); ChildNodeEntry cne = state.getChildNodeEntry(childId); if (cne == null) { // Child no longer in parent node, so remove it evict(child, false); } else { // Put all children into map of new children order // - regardless whether their position changed or // not - as we might need to reorder them later on. Path.Element newNameIndex = PathFactoryImpl.getInstance().createElement( cne.getName(), cne.getIndex()); newChildrenOrder.put(newNameIndex, child); if (!newNameIndex.equals(child.getPathElement())) { orderChanged = true; } } } } if (orderChanged) { /* If at least one child changed its position, reorder */ parent.setChildren(newChildrenOrder); } } checkConsistency(); } } /** * {@inheritDoc} */ public void nodeRemoved(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to get path of " + state.getNodeId(), e); } 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); } } } //------------------------------------------------------< private methods > /** * Return the first cached path that is mapped to given id. * * @param id node id * @return cached element, null if not found */ private PathMap.Element get(ItemId id) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { entry.touch(); return entry.getElements()[0]; } return null; } } /** * Return the nearest cached element in the path map, given a path. * The returned element is guaranteed to have an associated object that * is not null. * * @param path path * @return cached element, null if not found */ private PathMap.Element map(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, false); while (element != null) { LRUEntry entry = element.get(); if (entry != null) { entry.touch(); return element; } element = element.getParent(); } return null; } } /** * Cache an item in the hierarchy given its id and path. * * @param id node id * @param path path to item */ private void cache(NodeId id, Path path) { synchronized (cacheMonitor) { if (isCached(id, path)) { return; } if (idCache.size() >= upperLimit) { idCacheStatistics.log(); /** * Remove least recently used item. Scans the LRU list from * head to tail and removes the first item that has no children. */ LRUEntry entry = head; while (entry != null) { PathMap.Element[] elements = entry.getElements(); int childrenCount = 0; for (int i = 0; i < elements.length; i++) { childrenCount += elements[i].getChildrenCount(); } if (childrenCount == 0) { evictAll(entry.getId(), false); return; } entry = entry.getNext(); } } PathMap.Element element = pathCache.put(path); if (element.get() != null) { if (!id.equals((element.get()).getId())) { log.debug("overwriting PathMap.Element"); } } LRUEntry entry = idCache.get(id); if (entry == null) { entry = new LRUEntry(id, element); idCache.put(id, entry); } else { entry.addElement(element); } element.set(entry); checkConsistency(); } } /** * Return a flag indicating whether a certain node and/or path is cached. * If path is null, check whether the item is * cached at all. If path is not null, * check whether the item is cached with that path. * * @param id item id * @param path path, may be null * @return true if the item is already cached; * false otherwise */ boolean isCached(NodeId id, Path path) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry == null) { return false; } if (path == null) { return true; } PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i].hasPath(path)) { return true; } } return false; } } /** * Return a flag indicating whether a certain path is cached. * * @param path item path * @return true if the item is already cached; * false otherwise */ boolean isCached(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, true); if (element != null) { return element.get() != null; } return false; } } /** * Remove all path mapping for a given item id. Removes the associated * LRUEntry and the PathMap.Element with it. * Indexes of same name sibling elements are shifted! * * @param id item id */ private void evictAll(ItemId id, boolean shift) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { evict(elements[i], shift); } } checkConsistency(); } } /** * Evict path map element from cache. This will traverse all children * of this element and remove the objects associated with them. * Index of same name sibling items are shifted! * * @param element path map element */ private void evict(PathMap.Element element, boolean shift) { // assert: synchronized (cacheMonitor) element.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { LRUEntry entry = element.get(); if (entry.removeElement(element) == 0) { idCache.remove(entry.getId()); entry.remove(); } } }, false); element.remove(shift); } /** * Invoked when a notification about a child node addition has been received. * * @param state node state where child was added * @param path path to child node * @param id child node id * * @throws PathNotFoundException if the path was not found * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeAdded(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element element = null; LRUEntry entry = idCache.get(id); if (entry != null) { // child node already cached: this can have the following // reasons: // 1) node was moved, cached path is outdated // 2) node was cloned, cached path is still valid NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { PathMap.Element[] elements = entry.getElements(); element = elements[0]; for (int i = 0; i < elements.length; i++) { elements[i].remove(); } } } PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent != null) { parent.insert(path.getNameElement()); } if (element != null) { // store remembered element at new position pathCache.put(path, element); } } /** * Invoked when a notification about a child node removal has been received. * * @param state node state * @param path node path * @param id node id * * @throws PathNotFoundException if the path was not found. * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeRemoved(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent == null) { return; } PathMap.Element element = parent.getDescendant(path.getLastElement(), true); if (element != null) { // with SNS, this might evict a child that is NOT the one // having id, check first whether item has // the id passed as argument LRUEntry entry = element.get(); if (entry != null && !entry.getId().equals(id)) { return; } // if item is shareable, remove this path only, otherwise // every path this item has been mapped to NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { evictAll(id, true); } else { evict(element, true); } } else { // element itself is not cached, but removal might cause SNS // index shifting parent.remove(path.getNameElement()); } } /** * Dump contents of path map and elements included to a string. */ public String toString() { final StringBuilder builder = new StringBuilder(); synchronized (cacheMonitor) { pathCache.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { for (int i = 0; i < element.getDepth(); i++) { builder.append("--"); } builder.append(element.getName()); int index = element.getIndex(); if (index != 0 && index != 1) { builder.append('['); builder.append(index); builder.append(']'); } builder.append(" "); builder.append(element.get()); builder.append("\n"); } }, true); } return builder.toString(); } /** * Check consistency. */ private void checkConsistency() throws IllegalStateException { // assert: synchronized (cacheMonitor) if (!consistencyCheckEnabled) { return; } int elementsInCache = 0; Iterator iter = idCache.values().iterator(); while (iter.hasNext()) { LRUEntry entry = iter.next(); elementsInCache += entry.getElements().length; } class PathMapElementCounter implements PathMap.ElementVisitor { int count; public void elementVisited(PathMap.Element element) { LRUEntry mappedEntry = element.get(); LRUEntry cachedEntry = idCache.get(mappedEntry.getId()); if (cachedEntry == null) { String msg = "Path element (" + element + " ) cached in path map, associated id (" + mappedEntry.getId() + ") isn't."; throw new IllegalStateException(msg); } if (cachedEntry != mappedEntry) { String msg = "LRUEntry associated with element (" + element + " ) in path map is not equal to cached LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } PathMap.Element[] elements = cachedEntry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i] == element) { count++; return; } } String msg = "Element (" + element + ") cached in path map, but not in associated LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } } PathMapElementCounter counter = new PathMapElementCounter(); pathCache.traverse(counter, false); if (counter.count != elementsInCache) { String msg = "PathMap element and cached element count don't match (" + counter.count + " != " + elementsInCache + ")"; throw new IllegalStateException(msg); } } /** * 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 { /** * Previous entry */ private LRUEntry previous; /** * Next entry */ private LRUEntry next; /** * Node id */ private final NodeId id; /** * Elements in path map */ private PathMap.Element[] elements; /** * Create a new instance of this class * * @param id node id * @param element the path map element for this entry */ @SuppressWarnings("unchecked") public LRUEntry(NodeId id, PathMap.Element element) { this.id = id; this.elements = new PathMap.Element[] { element }; append(); } /** * Append entry to end of LRU list */ public void append() { if (tail == null) { head = this; tail = this; } else { previous = tail; tail.next = this; tail = this; } } /** * Remove entry from LRU list */ public void remove() { if (previous != null) { previous.next = next; } if (next != null) { next.previous = previous; } if (head == this) { head = next; } if (tail == this) { tail = previous; } previous = null; next = null; } /** * Touch entry. Removes it from its current position in the LRU list * and moves it to the end. */ public void touch() { remove(); append(); } /** * Return next LRU entry * * @return next LRU entry */ public LRUEntry getNext() { return next; } /** * Return node ID * * @return node ID */ public NodeId getId() { return id; } /** * Return elements in path map that are mapped to id. If * this entry is a shareable node or one of its descendant, it can * be reached by more than one path. * * @return element in path map */ public PathMap.Element[] getElements() { return elements; } /** * Add a mapping to some element. */ @SuppressWarnings("unchecked") public void addElement(PathMap.Element element) { PathMap.Element[] tmp = new PathMap.Element[elements.length + 1]; System.arraycopy(elements, 0, tmp, 0, elements.length); tmp[elements.length] = element; elements = tmp; } /** * Remove a mapping to some element from this entry. * * @return number of mappings left */ @SuppressWarnings("unchecked") public int removeElement(PathMap.Element element) { boolean found = false; for (int i = 0; i < elements.length; i++) { if (found) { elements[i - 1] = elements[i]; } else if (elements[i] == element) { found = true; } } if (found) { PathMap.Element[] tmp = new PathMap.Element[elements.length - 1]; System.arraycopy(elements, 0, tmp, 0, tmp.length); elements = tmp; } return elements.length; } /** * {@inheritDoc} */ public String toString() { return id.toString(); } } private final class CacheStatistics { private final String id; private final ReferenceMap cache; private long timeStamp = 0; public CacheStatistics() { this.id = cacheMonitor.toString(); this.cache = idCache; } public void log() { if (log.isDebugEnabled()) { long now = System.currentTimeMillis(); final String msg = "Cache id = {};size = {};max = {}"; if (log.isTraceEnabled()) { log.trace(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } else if (now > timeStamp + CACHE_STATISTICS_LOG_INTERVAL_MILLIS) { timeStamp = now; log.debug(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.security.Principal; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.security.AccessControlException; import javax.security.auth.Subject; import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.core.config.AccessManagerConfig; import org.apache.jackrabbit.core.config.LoginModuleConfig; import org.apache.jackrabbit.core.config.SecurityConfig; import org.apache.jackrabbit.core.config.SecurityManagerConfig; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; import org.apache.jackrabbit.core.config.UserManagerConfig; import org.apache.jackrabbit.core.security.AMContext; 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.GroupPrincipals; import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl; import org.apache.jackrabbit.core.security.principal.PrincipalProvider; import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl; import org.apache.jackrabbit.core.security.user.MembershipCache; import org.apache.jackrabbit.core.security.user.UserManagerImpl; import org.apache.jackrabbit.core.security.user.action.AuthorizableAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The security manager acts as central managing class for all security related * operations on a low-level non-protected level. It manages the * * {@link PrincipalProvider}s * {@link AccessControlProvider}s * {@link WorkspaceAccessManager} * {@link UserManager} * */ public class DefaultSecurityManager implements JackrabbitSecurityManager { /** * the default logger */ private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class); /** * Flag indicating if the security manager was properly initialized. */ private boolean initialized; /** * the repository implementation */ private RepositoryImpl repository; /** * System session. */ private SystemSession systemSession; /** * System user manager. Implementation needed here for the DefaultPrincipalProvider. */ private UserManager systemUserManager; /** * The user id of the administrator. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ADMIN_ID}). */ protected String adminId; /** * The user id of the anonymous user. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ANONYMOUS_ID}). */ protected String anonymousId; /** * Contains the access control providers per workspace. * key = name of the workspace, * value = {@link AccessControlProvider} */ private final Map acProviders = new HashMap(); /** * the AccessControlProviderFactory */ private AccessControlProviderFactory acProviderFactory; /** * the configured WorkspaceAccessManager */ private WorkspaceAccessManager workspaceAccessManager; /** * the principal provider registry */ private PrincipalProviderRegistry principalProviderRegistry; /** * factory for login-context {@see Repository#login()) */ private AuthContextProvider authContextProvider; //------------------------------------------< JackrabbitSecurityManager >--- /** * @see JackrabbitSecurityManager#init(Repository, Session) */ public synchronized void init(Repository repository, Session systemSession) throws RepositoryException { if (initialized) { throw new IllegalStateException("already initialized"); } if (!(repository instanceof RepositoryImpl)) { throw new RepositoryException("RepositoryImpl expected"); } if (!(systemSession instanceof SystemSession)) { throw new RepositoryException("SystemSession expected"); } this.systemSession = (SystemSession) systemSession; this.repository = (RepositoryImpl) repository; SecurityConfig config = this.repository.getConfig().getSecurityConfig(); LoginModuleConfig loginModConf = config.getLoginModuleConfig(); // build AuthContextProvider based on appName + optional LoginModuleConfig authContextProvider = new AuthContextProvider(config.getAppName(), loginModConf); if (authContextProvider.isLocal()) { log.info("init: use Repository Login-Configuration for " + config.getAppName()); } else if (authContextProvider.isJAAS()) { log.info("init: use JAAS login-configuration for " + config.getAppName()); } else { String msg = "Neither JAAS nor RepositoryConfig contained a valid configuration for " + config.getAppName(); log.error(msg); throw new RepositoryException(msg); } Properties[] moduleConfig = authContextProvider.getModuleConfig(); // retrieve default-ids (admin and anonymous) from login-module-configuration. for (Properties props : moduleConfig) { if (props.containsKey(LoginModuleConfig.PARAM_ADMIN_ID)) { adminId = props.getProperty(LoginModuleConfig.PARAM_ADMIN_ID); } if (props.containsKey(LoginModuleConfig.PARAM_ANONYMOUS_ID)) { anonymousId = props.getProperty(LoginModuleConfig.PARAM_ANONYMOUS_ID); } } // fallback: if (adminId == null) { log.debug("No adminID defined in LoginModule/JAAS config -> using default."); adminId = SecurityConstants.ADMIN_ID; } if (anonymousId == null) { log.debug("No anonymousID defined in LoginModule/JAAS config -> using default."); anonymousId = SecurityConstants.ANONYMOUS_ID; } // create the system userManager and make sure the system-users exist. systemUserManager = createUserManager(this.systemSession); createSystemUsers(systemUserManager, this.systemSession, adminId, anonymousId); // init default ac-provider-factory acProviderFactory = new AccessControlProviderFactoryImpl(); acProviderFactory.init(this.systemSession); // create the workspace access manager SecurityManagerConfig smc = config.getSecurityManagerConfig(); if (smc != null && smc.getWorkspaceAccessConfig() != null) { workspaceAccessManager = smc.getWorkspaceAccessConfig().newInstance(WorkspaceAccessManager.class); } else { // fallback -> the default implementation log.debug("No WorkspaceAccessManager configured; using default."); workspaceAccessManager = createDefaultWorkspaceAccessManager(); } workspaceAccessManager.init(this.systemSession); // initialize principal-provider registry // 1) create default PrincipalProvider defaultPP = createDefaultPrincipalProvider(moduleConfig); // 2) create registry instance principalProviderRegistry = new ProviderRegistryImpl(defaultPP); // 3) register all configured principal providers. for (Properties props : moduleConfig) { principalProviderRegistry.registerProvider(props); } initialized = true; } /** * @see JackrabbitSecurityManager#dispose(String) */ public void dispose(String workspaceName) { checkInitialized(); synchronized (acProviders) { AccessControlProvider prov = acProviders.remove(workspaceName); if (prov != null) { prov.close(); } } } /** * @see JackrabbitSecurityManager#close() */ public void close() { checkInitialized(); synchronized (acProviders) { for (AccessControlProvider accessControlProvider : acProviders.values()) { accessControlProvider.close(); } acProviders.clear(); } } /** * @see JackrabbitSecurityManager#getAccessManager(Session,AMContext) */ public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { checkInitialized(); AccessManagerConfig amConfig = repository.getConfig().getSecurityConfig().getAccessManagerConfig(); try { String wspName = session.getWorkspace().getName(); AccessControlProvider pp = getAccessControlProvider(wspName); AccessManager accessMgr; if (amConfig == null) { log.debug("No configuration entry for AccessManager. Using org.apache.jackrabbit.core.security.DefaultAccessManager"); accessMgr = new DefaultAccessManager(); } else { accessMgr = amConfig.newInstance(AccessManager.class); } accessMgr.init(amContext, pp, workspaceAccessManager); return accessMgr; } catch (AccessDeniedException e) { // re-throw throw e; } catch (Exception e) { // wrap in RepositoryException String clsName = (amConfig == null) ? "-- missing access manager configuration --" : amConfig.getClassName(); String msg = "Failed to instantiate AccessManager (" + clsName + ")"; log.error(msg, e); throw new RepositoryException(msg, e); } } /** * @see JackrabbitSecurityManager#getPrincipalManager(Session) */ public PrincipalManager getPrincipalManager(Session session) throws RepositoryException { checkInitialized(); if (session instanceof SessionImpl) { SessionImpl sImpl = (SessionImpl) session; return createPrincipalManager(sImpl); } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserManager(Session) */ public UserManager getUserManager(Session session) throws RepositoryException { checkInitialized(); if (session == systemSession) { return systemUserManager; } else if (session instanceof SessionImpl) { String workspaceName = systemSession.getWorkspace().getName(); try { SessionImpl sImpl = (SessionImpl) session; UserManagerImpl uMgr; if (workspaceName.equals(sImpl.getWorkspace().getName())) { uMgr = createUserManager(sImpl); } else { SessionImpl s = (SessionImpl) sImpl.createSession(workspaceName); uMgr = createUserManager(s); sImpl.addListener(uMgr); } return uMgr; } catch (NoSuchWorkspaceException e) { throw new AccessControlException("Cannot build UserManager for " + session.getUserID(), e); } } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserID(javax.security.auth.Subject, String) */ public String getUserID(Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); // 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(); if (cl != null) { Set s = subject.getPrincipals(cl); if (!s.isEmpty()) { for (Principal p : s) { if (!GroupPrincipals.isGroup(p)) { return p.getName(); } } // all principals found with the given p-Class were Group principals log.debug("Only Group principals found with class '" + cl.getName() + "' -> Not used for UserID."); } else { log.debug("No principal found with class '" + cl.getName() + "'."); } } /* Fallback scenario to retrieve userID from the subject: Since the subject may contain multiple principals and the principal name may not be equals to the UserID, the id is retrieved by searching for the corresponding authorizable and if this doesn't succeed an attempt is made to obtained it from the login-credentials. */ String uid = null; // first try to retrieve an authorizable corresponding to // a non-group principal. the first one present is used // to determine the userID. try { UserManager umgr = getSystemUserManager(workspaceName); for (Principal p : subject.getPrincipals()) { if (!(p instanceof Group)) { Authorizable authorz = umgr.getAuthorizable(p); if (authorz != null && !authorz.isGroup()) { uid = authorz.getID(); break; } } } } catch (RepositoryException e) { // failed to access userid via user manager -> use fallback 2. log.error("Unexpected error while retrieving UserID.", e); } // 2. if no matching user is found try simple access to userID over // SimpleCredentials. if (uid == null) { Iterator creds = subject.getPublicCredentials( SimpleCredentials.class).iterator(); if (creds.hasNext()) { SimpleCredentials sc = creds.next(); uid = sc.getUserID(); } } return uid; } /** * Creates an AuthContext for the given {@link Credentials} and * {@link Subject}. The workspace name is ignored and users are * stored and retrieved from a specific (separate) workspace. * This includes selection of application specific LoginModules and * initialization with credentials and Session to System-Workspace * * @return an {@link AuthContext} for the given Credentials, Subject * @throws RepositoryException in other exceptional repository states */ public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); return getAuthContextProvider().getAuthContext(creds, subject, systemSession, getPrincipalProviderRegistry(), adminId, anonymousId); } //----------------------------------------------------------< protected >--- /** * @return The SecurityManagerConfig configured for the * repository this manager has been created for. */ protected SecurityManagerConfig getConfig() { return repository.getConfig().getSecurityConfig().getSecurityManagerConfig(); } /** * @param workspaceName The name of the target workspace. * @return The system user manager. Since this implementation stores users * in a dedicated workspace the system user manager is the same for all * sessions irrespective of the workspace. * @throws javax.jcr.RepositoryException If an error occurs. */ protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException { return systemUserManager; } /** * @param session The session for which to retrieve the membership cache. * @return The membership cache. * @throws RepositoryException If an error occurs. */ protected MembershipCache getMembershipCache(SessionImpl session) throws RepositoryException { if (session == systemSession || session instanceof SystemSession) { // force creation of the membership cache within the corresponding uMgr return null; } else { return ((UserManagerImpl) getSystemUserManager(session.getWorkspace().getName())).getMembershipCache(); } } /** * Creates a {@link UserManagerImpl} for the given session. May be overridden * to return a custom implementation. * * @param session session * @return user manager * @throws RepositoryException if an error occurs */ protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException { UserManagerConfig umc = getConfig().getUserManagerConfig(); UserManagerImpl um; if (umc != null) { Class>[] paramTypes = new Class[] { SessionImpl.class, String.class, Properties.class, MembershipCache.class}; um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class, paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session)); } else { um = new UserManagerImpl(session, adminId, null, getMembershipCache(session)); } if (umc != null && !(session instanceof SystemSession)) { AuthorizableAction[] actions = umc.getAuthorizableActions(); um.setAuthorizableActions(actions); } return um; } /** * @param session The session used to create the principal manager. * @return A new instance of PrincipalManagerImpl * @throws javax.jcr.RepositoryException If an error occurs. */ protected PrincipalManager createPrincipalManager(SessionImpl session) throws RepositoryException { return new PrincipalManagerImpl(session, getPrincipalProviderRegistry().getProviders()); } /** * @return A nwe instance of WorkspaceAccessManagerImpl to be used as * default workspace access manager if the configuration doesn't specify one. */ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { return new WorkspaceAccessManagerImpl(); } /** * Creates the default principal provider used to create the * {@link PrincipalProviderRegistry}. * * @return An new instance of DefaultPrincipalProvider. * @throws RepositoryException If an error occurs. */ protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { boolean initialized = false; PrincipalProvider defaultPP = new DefaultPrincipalProvider(this.systemSession, (UserManagerImpl) systemUserManager); 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; } /** * @return The PrincipalProviderRegistry created during initialization. */ protected PrincipalProviderRegistry getPrincipalProviderRegistry() { return principalProviderRegistry; } /** * @return The AuthContextProvider created during initialization. */ protected AuthContextProvider getAuthContextProvider() { return authContextProvider; } /** * Throws IllegalStateException if this manager hasn't been * initialized. */ protected void checkInitialized() { if (!initialized) { throw new IllegalStateException("Not initialized"); } } /** * @return The system session used to initialize this SecurityManager. */ protected Session getSystemSession() { return systemSession; } /** * @return The repository used to initialize this SecurityManager. */ protected Repository getRepository() { return repository; } //-------------------------------------------------------------------------- /** * Returns the access control provider for the specified * workspaceName. * * @param workspaceName Name of the workspace. * @return access control provider * @throws NoSuchWorkspaceException If no workspace with 'workspaceName' exists. * @throws RepositoryException */ private AccessControlProvider getAccessControlProvider(String workspaceName) throws NoSuchWorkspaceException, RepositoryException { checkInitialized(); AccessControlProvider provider = acProviders.get(workspaceName); if (provider == null || !provider.isLive()) { // mark this workspace as 'active' so the workspace does not // get disposed by the workspace-janitor // TODO: There should be a cleaner way to do this. repository.markWorkspaceActive(workspaceName); WorkspaceSecurityConfig secConf = null; WorkspaceConfig conf = repository.getConfig().getWorkspaceConfig(workspaceName); if (conf != null) { secConf = conf.getSecurityConfig(); } provider = acProviderFactory.createProvider( repository.getSystemSession(workspaceName), secConf); synchronized (acProviders) { acProviders.put(workspaceName, provider); } } return provider; } /** * Make sure the system users (admin and anonymous) exist. * * @param userManager Manager to create users/groups. * @param session The editing session. * @param adminId UserID of the administrator. * @param anonymousId UserID of the anonymous user. * @throws RepositoryException If an error occurs. */ static void createSystemUsers(UserManager userManager, SystemSession session, String adminId, String anonymousId) throws RepositoryException { Authorizable admin; if (adminId != null) { admin = userManager.getAuthorizable(adminId); if (admin == null) { userManager.createUser(adminId, adminId); if (!userManager.isAutoSave()) { session.save(); } log.info("... created admin-user with id \'" + adminId + "\' ..."); } } if (anonymousId != null) { Authorizable anonymous = userManager.getAuthorizable(anonymousId); if (anonymous == null) { try { userManager.createUser(anonymousId, ""); if (!userManager.isAutoSave()) { session.save(); } log.info("... created anonymous user with id \'" + anonymousId + "\' ..."); } catch (RepositoryException e) { // exception while creating the anonymous user. // log an error but don't abort the repository start-up log.error("Failed to create anonymous user.", e); } } } } //------------------------------------------------------< inner classes >--- /** * WorkspaceAccessManager that upon {@link #grants(Set principals, String)} * evaluates if access to the root node of a workspace with the specified * name is granted. */ private final class WorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { //-----------------------------------------< WorkspaceAccessManager >--- /** * {@inheritDoc} */ public void init(Session systemSession) throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public void close() throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public boolean grants(Set principals, String workspaceName) throws RepositoryException { AccessControlProvider prov = getAccessControlProvider(workspaceName); return prov.canAccessRoot(principals); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ItemNotFoundException; import javax.jcr.RepositoryException; 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.spi.Name; import org.apache.jackrabbit.spi.Path; /** * The HierarchyManager interface ... */ public interface HierarchyManager { /** * Resolves a path into an item id. * * If there is both a node and a property at the specified path, this method * will return the id of the node. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * item to be found at path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #resolveNodePath(Path)} and * {@link #resolvePropertyPath(Path)} should be used instead. * * @param path path to resolve * @return item id referred to by path or null * if there's no item at path. * @throws RepositoryException if an error occurs */ @Deprecated ItemId resolvePath(Path path) throws RepositoryException; /** * Resolves a path into a node id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * node to be found at path. * * @param path path to resolve * @return node id referred to by path or null * if there's no node at path. * @throws RepositoryException if an error occurs */ NodeId resolveNodePath(Path path) throws RepositoryException; /** * Resolves a path into a property id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * property to be found at path. * * @param path path to resolve * @return property id referred to by path or null * if there's no property at path. * @throws RepositoryException if an error occurs */ PropertyId resolvePropertyPath(Path path) throws RepositoryException; /** * Returns the path to the given item. * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item. * @param id id of item whose name should be returned * @return * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item, with the given parent id. If the * given item is not shareable, this is identical to {@link #getName(ItemId)}. * * @param id node id * @param parentId parent node id * @return name * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified item which is equivalent to * getPath(id).getAncestorCount(). The depth reflects the * absolute hierarchy level. * * @param id item id * @return the depth of the specified item * @throws ItemNotFoundException if the specified id does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified descendant relative to the given * ancestor. If ancestorId and descendantId * denote the same item 0 is returned. If ancestorId does not * denote an ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestorId does not * denote an ancestor of the item denoted by descendantId * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; /** * Determines whether the node with the specified nodeId * is an ancestor of the item denoted by the given itemId. * This is equivalent to * getPath(nodeId).isAncestorOf(getPath(itemId)). * * @param nodeId node id * @param itemId item id * @return true if the node with the specified * nodeId is an ancestor of the item denoted by the * given itemId; false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException; //------------------------------------------- operation with shareable nodes /** * Determines whether the node with the specified ancestor * is a share ancestor of the item denoted by the given descendant. * This is true for two nodes A, B * if either: * * A is a (proper) ancestor of B * there is a non-empty sequence of nodes N1,... * ,Nk such that A= * N1 and B=Nk * and Ni is the parent or a share-parent of * Ni+1 (for every i in 1 * ...k-1. * * * @param ancestor node id * @param descendant item id * @return true if the node denoted by ancestor * is a share ancestor of the item denoted by descendant, * false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified share-descendant relative to the given * share-ancestor. If ancestor and descendant * denote the same item, 0 is returned. If ancestor * does not denote an share-ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestor does * not denote a share-ancestor of the item denoted by descendant * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getShareRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; 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.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; 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.PropertyState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HierarchyManagerImpl ... */ public class HierarchyManagerImpl implements HierarchyManager { private static Logger log = LoggerFactory.getLogger(HierarchyManagerImpl.class); /** * The parent name returned for orphaned or root nodes. * TODO: Is it proper to use an invalid Name for this. */ private static final Name EMPTY_NAME = NameFactoryImpl.getInstance().create("", ""); protected final NodeId rootNodeId; protected final ItemStateManager provider; /** * Flags describing what items to return in {@link #resolvePath(Path, int)}. */ static final int RETURN_NODE = 1; static final int RETURN_PROPERTY = 2; static final int RETURN_ANY = (RETURN_NODE | RETURN_PROPERTY); public HierarchyManagerImpl(NodeId rootNodeId, ItemStateManager provider) { this.rootNodeId = rootNodeId; this.provider = provider; } public NodeId getRootNodeId() { return rootNodeId; } //-------------------------------------------------------< implementation > /** * Internal implementation that iteratively resolves a path into an item. * * @param elements path elements * @param next index of next item in elements to inspect * @param id id of item at path elements[0]..elements[next - 1] * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws ItemStateException if an intermediate item state is not found * @throws MalformedPathException if building an intermediate path fails */ protected ItemId resolvePath(Path.Element[] elements, int next, ItemId id, int typesAllowed) throws ItemStateException, MalformedPathException { PathBuilder builder = new PathBuilder(); for (int i = 0; i < next; i++) { builder.addLast(elements[i]); } for (int i = next; i < elements.length; i++) { Path.Element elem = elements[i]; NodeId parentId = (NodeId) id; id = null; Name name = elem.getName(); int index = elem.getIndex(); if (index == 0) { index = 1; } int typeExpected = typesAllowed; if (i < elements.length - 1) { // intermediate items must always be nodes typeExpected = RETURN_NODE; } NodeState parentState = (NodeState) getItemState(parentId); if ((typeExpected & RETURN_NODE) != 0) { ChildNodeEntry nodeEntry = getChildNodeEntry(parentState, name, index); if (nodeEntry != null) { id = nodeEntry.getId(); } } if (id == null && (typeExpected & RETURN_PROPERTY) != 0) { if (parentState.hasPropertyName(name) && (index <= 1)) { // property id = new PropertyId(parentState.getNodeId(), name); } } if (id == null) { break; } builder.addLast(elements[i]); pathResolved(id, builder); } return id; } //---------------------------------------------------------< overridables > /** * Return an item state, given its item id. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return item state * @throws NoSuchItemStateException if the item does not exist * @throws ItemStateException if an error occurs * @see ZombieHierarchyManager#getItemState(ItemId) */ protected ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { return provider.getItemState(id); } /** * Determines whether an item state for a given item id exists. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return true if an item state exists, otherwise * false * @see ZombieHierarchyManager#hasItemState(ItemId) */ protected boolean hasItemState(ItemId id) { return provider.hasItemState(id); } /** * Returns the parentUUID of the given item. * * Low-level hook provided for specialized derived classes. * * @param state item state * @return parentUUID of the given item * @see ZombieHierarchyManager#getParentId(ItemState) */ protected NodeId getParentId(ItemState state) { return state.getParentId(); } /** * Return all parents of a node. A shareable node has possibly more than * one parent. * * @param state item state * @param useOverlayed whether to use overlayed state for shareable nodes * @return set of parent NodeIds. If state has no parent, * array has length 0. */ protected Set getParentIds(ItemState state, boolean useOverlayed) { if (state.isNode()) { // if this is a node, quickly check whether it is shareable and // whether it contains more than one parent NodeState ns = (NodeState) state; if (ns.isShareable() && useOverlayed && ns.hasOverlayedState()) { ns = (NodeState) ns.getOverlayedState(); } Set s = ns.getSharedSet(); if (s.size() > 1) { return s; } } NodeId parentId = getParentId(state); if (parentId != null) { LinkedHashSet s = new LinkedHashSet(); s.add(parentId); return s; } return Collections.emptySet(); } /** * Returns the ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
InputStream
TestAll
null
BatchedItemOperations
ItemState
srcAbsPath
destAbsPath
mix:shareable
srcPath
removeExisting
true
srcWorkspace
NodeState
destPath
* Precondition: the state manager needs to be in edit mode. * * @param srcPath * @param destPath * @param flag one of *
COPY
CLONE
CLONE_REMOVE_EXISTING
srcStateMgr
* Precondition: the state manager needs to be in edit mode. * * @param srcPath * @param srcStateMgr * @param srcHierMgr * @param srcAccessMgr * @param destPath * @param flag one of *
* Precondition: the state manager needs to be in edit mode. * * @param srcPath * @param destPath * @return the id of the moved node * @throws ConstraintViolationException * @throws VersionException * @throws AccessDeniedException * @throws PathNotFoundException * @throws ItemExistsException * @throws LockException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode */ public NodeId move(Path srcPath, Path destPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot move path " + safeGetJCRPath(srcPath) + " because manager is not in edit mode"); } // 1. check paths & retrieve state try { if (srcPath.isAncestorOf(destPath)) { String msg = safeGetJCRPath(destPath) + ": invalid destination path" + " (cannot be descendant of source path)"; log.debug(msg); throw new RepositoryException(msg); } } catch (MalformedPathException mpe) { String msg = "invalid path for move: " + safeGetJCRPath(destPath); log.debug(msg); throw new RepositoryException(msg, mpe); } Path srcParentPath = srcPath.getAncestor(1); NodeState target = getNodeState(srcPath); NodeState srcParent = getNodeState(srcParentPath); Path destParentPath = destPath.getAncestor(1); NodeState destParent = getNodeState(destParentPath); int ind = destPath.getIndex(); if (ind > 0) { // subscript in name element String msg = safeGetJCRPath(destPath) + ": invalid destination path" + " (subscript in name element is not allowed)"; log.debug(msg); throw new RepositoryException(msg); } HierarchyManagerImpl hierMgr = (HierarchyManagerImpl) this.hierMgr; if (hierMgr.isShareAncestor(target.getNodeId(), destParent.getNodeId())) { String msg = safeGetJCRPath(destPath) + ": invalid destination path" + " (share cycle detected)"; log.debug(msg); throw new RepositoryException(msg); } // 2. check if target state can be removed from old/added to new parent checkRemoveNode(target, srcParent.getNodeId(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); checkAddNode(destParent, destPath.getName(), target.getNodeTypeName(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); // 3. do move operation (modify and store affected states) boolean renameOnly = srcParent.getNodeId().equals(destParent.getNodeId()); int srcNameIndex = srcPath.getIndex(); if (srcNameIndex == 0) { srcNameIndex = 1; } stateMgr.store(target); if (renameOnly) { stateMgr.store(srcParent); // change child node entry destParent.renameChildNodeEntry(srcPath.getName(), srcNameIndex, destPath.getName()); } else { // check shareable case if (target.isShareable()) { String msg = "Moving a shareable node (" + safeGetJCRPath(srcPath) + ") is not supported."; log.debug(msg); 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())) { // 2. re-parent target node target.setParentId(destParent.getNodeId()); // 3. add child node entry to new parent destParent.addChildNodeEntry(destPath.getName(), target.getNodeId()); } } return target.getNodeId(); } /** * Removes the specified node, recursively removing its properties and * child nodes. *
* Precondition: the state manager needs to be in edit mode. * * @param nodePath * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws LockException * @throws ItemNotFoundException * @throws ReferentialIntegrityException * @throws RepositoryException * @throws IllegalStateException */ public void removeNode(Path nodePath) throws ConstraintViolationException, AccessDeniedException, VersionException, LockException, ItemNotFoundException, ReferentialIntegrityException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot remove node (" + safeGetJCRPath(nodePath) + ") because manager is not in edit mode"); } // 1. retrieve affected state NodeState target = getNodeState(nodePath); NodeId parentId = target.getParentId(); // 2. check if target state can be removed from parent checkRemoveNode(target, parentId, CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_REFERENCES | CHECK_HOLD | CHECK_RETENTION); // 3. do remove operation removeNodeState(target); } //--------------------------------------< misc. high-level helper methods > /** * Checks if adding a child node called nodeName of node type * nodeTypeName to the given parent node is allowed in the * current context. * * @param parentState * @param nodeName * @param nodeTypeName * @param options bit-wise OR'ed flags specifying the checks that should be * performed; any combination of the following constants: *
nodeName
nodeTypeName
{@link #CHECK_ACCESS}
{@link #CHECK_LOCK}
{@link #CHECK_CHECKED_OUT}
{@link #CHECK_CONSTRAINTS}
{@link #CHECK_HOLD}
{@link #CHECK_RETENTION}
{@link #CHECK_REFERENCES}
nodePath
* Note that access rights are not enforced! *
* Precondition: the state manager needs to be in edit mode. * * @param parent * @param nodeName * @param nodeTypeName * @param mixinNames * @param id * @return * @throws ItemExistsException * @throws ConstraintViolationException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode. */ public NodeState createNodeState(NodeState parent, Name nodeName, Name nodeTypeName, Name[] mixinNames, NodeId id) throws ItemExistsException, ConstraintViolationException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot create node state for " + nodeName + " because manager is not in edit mode"); } QNodeDefinition def = findApplicableNodeDefinition(nodeName, nodeTypeName, parent); return createNodeState(parent, nodeName, nodeTypeName, mixinNames, id, def); } /** * Creates a new node based on the given definition. *
* Precondition: the state manager needs to be in edit mode. * * @param parent * @param nodeName * @param nodeTypeName * @param mixinNames * @param id * @param def * @return * @throws ItemExistsException * @throws ConstraintViolationException * @throws RepositoryException * @throws IllegalStateException */ public NodeState createNodeState(NodeState parent, Name nodeName, Name nodeTypeName, Name[] mixinNames, NodeId id, QNodeDefinition def) throws ItemExistsException, ConstraintViolationException, RepositoryException, IllegalStateException { // check for name collisions with existing nodes if (!def.allowsSameNameSiblings() && parent.hasChildNodeEntry(nodeName)) { NodeId errorId = parent.getChildNodeEntry(nodeName, 1).getId(); throw new ItemExistsException(safeGetJCRPath(errorId)); } if (nodeTypeName == null) { // no primary node type specified, // try default primary type from definition nodeTypeName = def.getDefaultPrimaryType(); if (nodeTypeName == null) { String msg = "an applicable node type could not be determined for " + nodeName; log.debug(msg); throw new ConstraintViolationException(msg); } } NodeState node = stateMgr.createNew(id, nodeTypeName, parent.getNodeId()); if (mixinNames != null && mixinNames.length > 0) { node.setMixinTypeNames(new HashSet(Arrays.asList(mixinNames))); } // now add new child node entry to parent parent.addChildNodeEntry(nodeName, node.getNodeId()); EffectiveNodeType ent = getEffectiveNodeType(node); // check shareable if (ent.includesNodeType(NameConstants.MIX_SHAREABLE)) { node.addShare(parent.getNodeId()); } if (!node.getMixinTypeNames().isEmpty()) { // create jcr:mixinTypes property QPropertyDefinition pd = ent.getApplicablePropertyDef(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true); createPropertyState(node, pd.getName(), pd.getRequiredType(), pd); } // add 'auto-create' properties defined in node type for (QPropertyDefinition pd : ent.getAutoCreatePropDefs()) { createPropertyState(node, pd.getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (QNodeDefinition nd : ent.getAutoCreateNodeDefs()) { createNodeState(node, nd.getName(), nd.getDefaultPrimaryType(), null, null, nd); } // store node stateMgr.store(node); // store parent stateMgr.store(parent); return node; } /** * Creates a new property. * * Note that access rights are not enforced! * * Precondition: the state manager needs to be in edit mode. * * @param parent * @param propName * @param type * @param numValues * @return * @throws ItemExistsException * @throws ConstraintViolationException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode */ public PropertyState createPropertyState(NodeState parent, Name propName, int type, int numValues) throws ItemExistsException, ConstraintViolationException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot create property state for " + propName + " because manager is not in edit mode"); } // find applicable definition QPropertyDefinition def; // multi- or single-valued property? if (numValues == 1) { // could be single- or multi-valued (n == 1) try { // try single-valued def = findApplicablePropertyDefinition(propName, type, false, parent); } catch (ConstraintViolationException cve) { // try multi-valued def = findApplicablePropertyDefinition(propName, type, true, parent); } } else { // can only be multi-valued (n == 0 || n > 1) def = findApplicablePropertyDefinition(propName, type, true, parent); } return createPropertyState(parent, propName, type, def); } /** * Creates a new property based on the given definition. * * Note that access rights are not enforced! * * Precondition: the state manager needs to be in edit mode. * * @param parent * @param propName * @param type * @param def * @return * @throws ItemExistsException * @throws RepositoryException */ public PropertyState createPropertyState(NodeState parent, Name propName, int type, QPropertyDefinition def) throws ItemExistsException, RepositoryException { // check for name collisions with existing properties if (parent.hasPropertyName(propName)) { PropertyId errorId = new PropertyId(parent.getNodeId(), propName); throw new ItemExistsException(safeGetJCRPath(errorId)); } // create property PropertyState prop = stateMgr.createNew(propName, parent.getNodeId()); if (def.getRequiredType() != PropertyType.UNDEFINED) { prop.setType(def.getRequiredType()); } else if (type != PropertyType.UNDEFINED) { prop.setType(type); } else { prop.setType(PropertyType.STRING); } prop.setMultiValued(def.isMultiple()); // compute system generated values if necessary new NodeTypeInstanceHandler(session.getUserID()).setDefaultValues( prop, parent, def); // now add new property entry to parent parent.addPropertyName(propName); // store parent stateMgr.store(parent); return prop; } /** * Unlinks the specified node state from its parent and recursively * removes it including its properties and child nodes. * * 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 * subject to the following checks: access rights, locking, versioning. * * @param target * @throws RepositoryException if an error occurs */ public void removeNodeState(NodeState target) throws RepositoryException { NodeId parentId = target.getParentId(); if (parentId == null) { String msg = "root node cannot be removed"; log.debug(msg); throw new RepositoryException(msg); } // remove target recursiveRemoveNodeState(target); // remove child node entry from parent NodeState parent = getNodeState(parentId); parent.removeChildNodeEntry(target.getNodeId()); // store parent stateMgr.store(parent); } /** * Retrieves the state of the node at the given path. * * Note that access rights are not enforced! * * @param nodePath * @return * @throws PathNotFoundException * @throws RepositoryException */ public NodeState getNodeState(Path nodePath) throws PathNotFoundException, RepositoryException { return getNodeState(stateMgr, hierMgr, nodePath); } /** * Retrieves the state of the node with the given id. * * Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public NodeState getNodeState(NodeId id) throws ItemNotFoundException, RepositoryException { return (NodeState) getItemState(stateMgr, id); } /** * Retrieves the state of the property with the given id. * * Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public PropertyState getPropertyState(PropertyId id) throws ItemNotFoundException, RepositoryException { return (PropertyState) getItemState(stateMgr, id); } /** * Retrieves the state of the item with the given id. * * Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public ItemState getItemState(ItemId id) throws ItemNotFoundException, RepositoryException { return getItemState(stateMgr, id); } //----------------------------------------------------< protected methods > /** * Verifies that the node at nodePath is checked-out; throws a * VersionException if that's not the case. * * A node is considered checked-out 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 * ancestors. * * @param nodePath * @throws PathNotFoundException * @throws VersionException * @throws RepositoryException */ protected void verifyCheckedOut(Path nodePath) throws PathNotFoundException, VersionException, RepositoryException { // search nearest ancestor that is versionable, start with node at nodePath /** * FIXME should not only rely on existence of jcr:isCheckedOut property * but also verify that node.isNodeType("mix:versionable")==true; * this would have a negative impact on performance though... */ NodeState nodeState = getNodeState(nodePath); while (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { if (nodePath.denotesRoot()) { return; } nodePath = nodePath.getAncestor(1); nodeState = getNodeState(nodePath); } PropertyId propId = new PropertyId(nodeState.getNodeId(), NameConstants.JCR_ISCHECKEDOUT); PropertyState propState; try { propState = (PropertyState) stateMgr.getItemState(propId); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + safeGetJCRPath(propId); log.debug(msg); throw new RepositoryException(msg, ise); } boolean checkedOut = propState.getValues()[0].getBoolean(); if (!checkedOut) { throw new VersionException(safeGetJCRPath(nodePath) + " is checked-in"); } } /** * Verifies that the node at nodePath is not locked by * somebody else than the current session. * * @param nodePath path of node to check * @throws PathNotFoundException * @throws LockException if write access to the specified path is not allowed * @throws RepositoryException if another error occurs */ protected void verifyUnlocked(Path nodePath) throws LockException, RepositoryException { // make sure there's no foreign lock on node at nodePath context.getWorkspace().getInternalLockManager().checkLock( nodePath, session); } /** * Verifies that the node at nodePath is not protected. * * @param nodePath path of node to check * @throws PathNotFoundException if no node exists at nodePath * @throws ConstraintViolationException if write access to the specified * path is not allowed * @throws RepositoryException if another error occurs */ protected void verifyNotProtected(Path nodePath) throws PathNotFoundException, ConstraintViolationException, RepositoryException { NodeState node = getNodeState(nodePath); if (context.getItemManager().getDefinition(node).isProtected()) { throw new ConstraintViolationException(safeGetJCRPath(nodePath) + ": node is protected"); } } /** * Retrieves the state of the node at nodePath using the given * item state manager. * * Note that access rights are not enforced! * * @param srcStateMgr * @param srcHierMgr * @param nodePath * @return * @throws PathNotFoundException * @throws RepositoryException */ protected NodeState getNodeState(ItemStateManager srcStateMgr, HierarchyManager srcHierMgr, Path nodePath) throws PathNotFoundException, RepositoryException { try { NodeId id = srcHierMgr.resolveNodePath(nodePath); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(nodePath)); } return (NodeState) getItemState(srcStateMgr, id); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(nodePath)); } } /** * Retrieves the state of the item with the specified id using the given * item state manager. * * Note that access rights are not enforced! * * @param srcStateMgr * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ protected ItemState getItemState(ItemStateManager srcStateMgr, ItemId id) throws ItemNotFoundException, RepositoryException { try { return srcStateMgr.getItemState(id); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(safeGetJCRPath(id)); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + safeGetJCRPath(id); log.debug(msg); throw new RepositoryException(msg, ise); } } //------------------------------------------------------< private methods > /** * Recursively removes the given node state including its properties and * child nodes. * * The removal of child nodes is subject to the following checks: * access rights, locking & versioning status. Referential integrity * (references) is checked on commit. * * Note that the child node entry refering to targetState is * not automatically removed from targetState's * parent. * * @param targetState * @throws RepositoryException if an error occurs */ private void recursiveRemoveNodeState(NodeState targetState) throws RepositoryException { if (targetState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(targetState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); NodeId nodeId = entry.getId(); try { NodeState nodeState = (NodeState) stateMgr.getItemState(nodeId); // check if child node can be removed // (access rights, locking & versioning status as well // as retention and hold); // referential integrity (references) is checked // on commit checkRemoveNode(nodeState, targetState.getNodeId(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_HOLD | CHECK_RETENTION ); // remove child node recursiveRemoveNodeState(nodeState); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } // remove child node entry targetState.removeChildNodeEntry(entry.getName(), entry.getIndex()); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(targetState.getPropertyNames()); for (Name propName : tmp) { PropertyId propId = new PropertyId(targetState.getNodeId(), propName); try { PropertyState propState = (PropertyState) stateMgr.getItemState(propId); // remove property entry targetState.removePropertyName(propId.getName()); // destroy property state stateMgr.destroy(propState); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + propId; log.debug(msg); throw new RepositoryException(msg, ise); } } // now actually do unlink target state targetState.setParentId(null); // destroy target state (pass overlayed state since target state // might have been modified during unlinking) stateMgr.destroy(targetState.getOverlayedState()); } /** * Recursively copies the specified node state including its properties and * child nodes. * * @param srcState * @param srcPath * @param srcStateMgr * @param srcAccessMgr * @param destParentId * @param flag one of * * COPY * CLONE * CLONE_REMOVE_EXISTING * * @param refTracker tracks uuid mappings and processed reference properties * @return a deep copy of the given node state and its children * @throws RepositoryException if an error occurs */ private NodeState copyNodeState(NodeState srcState, Path srcPath, ItemStateManager srcStateMgr, AccessManager srcAccessMgr, NodeId destParentId, int flag, ReferenceChangeTracker refTracker) throws RepositoryException { NodeState newState; try { NodeId id = null; EffectiveNodeType ent = getEffectiveNodeType(srcState); boolean referenceable = ent.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean versionable = ent.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE); boolean fullVersionable = ent.includesNodeType(NameConstants.MIX_VERSIONABLE); boolean shareable = ent.includesNodeType(NameConstants.MIX_SHAREABLE); switch (flag) { case COPY: /* if this node is shareable and another node in the same shared set * has been already been copied and given a new uuid, use this one * (see section 14.5 of the specification) */ if (shareable && refTracker.getMappedId(srcState.getNodeId()) != null) { NodeId newId = refTracker.getMappedId(srcState.getNodeId()); NodeState sharedState = (NodeState) stateMgr.getItemState(newId); sharedState.addShare(destParentId); return sharedState; } break; case CLONE: if (!referenceable) { // non-referenceable node: always create new node id break; } // use same uuid as source node id = srcState.getNodeId(); if (stateMgr.hasItemState(id)) { if (shareable) { NodeState sharedState = (NodeState) stateMgr.getItemState(id); sharedState.addShare(destParentId); return sharedState; } // node with this uuid already exists throw new ItemExistsException(safeGetJCRPath(id)); } break; case CLONE_REMOVE_EXISTING: if (!referenceable) { // non-referenceable node: always create new node id break; } // use same uuid as source node id = srcState.getNodeId(); if (stateMgr.hasItemState(id)) { NodeState existingState = (NodeState) stateMgr.getItemState(id); // make sure existing node is not the parent // or an ancestor thereof if (id.equals(destParentId) || hierMgr.isAncestor(id, destParentId)) { String msg = "cannot remove node " + safeGetJCRPath(srcPath) + " because it is an ancestor of the destination"; log.debug(msg); throw new RepositoryException(msg); } // check if existing can be removed // (access rights, locking & versioning status, // node type constraints and retention/hold) checkRemoveNode(existingState, CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); // do remove existing removeNodeState(existingState); } break; default: throw new IllegalArgumentException( "unknown flag for copying node state: " + flag); } newState = stateMgr.createNew(id, srcState.getNodeTypeName(), destParentId); id = newState.getNodeId(); if (flag == COPY && referenceable) { // remember uuid mapping refTracker.mappedId(srcState.getNodeId(), id); } // copy node state newState.setMixinTypeNames(srcState.getMixinTypeNames()); if (shareable) { // initialize shared set newState.addShare(destParentId); } // copy child nodes for (ChildNodeEntry entry : srcState.getChildNodeEntries()) { Path srcChildPath = PathFactoryImpl.getInstance().create(srcPath, entry.getName(), true); if (!srcAccessMgr.isGranted(srcChildPath, Permission.READ)) { continue; } NodeId nodeId = entry.getId(); NodeState srcChildState = (NodeState) srcStateMgr.getItemState(nodeId); /** * special handling required for child nodes with special semantics * (e.g. those defined by nt:version, et.al.) * * todo FIXME delegate to 'node type instance handler' */ /** * If child is shareble and its UUID has already been remapped, * then simply add a reference to the state with that remapped * UUID instead of copying the whole subtree. */ if (srcChildState.isShareable()) { NodeId mappedId = refTracker.getMappedId(srcChildState.getNodeId()); if (mappedId != null) { if (stateMgr.hasItemState(mappedId)) { NodeState destState = (NodeState) stateMgr.getItemState(mappedId); if (!destState.isShareable()) { String msg = "Remapped child (" + safeGetJCRPath(srcPath) + ") is not shareable."; throw new ItemStateException(msg); } if (!destState.addShare(id)) { String msg = "Unable to add share to node: " + id; throw new ItemStateException(msg); } stateMgr.store(destState); newState.addChildNodeEntry(entry.getName(), mappedId); continue; } } } // recursive copying of child node NodeState newChildState = copyNodeState(srcChildState, srcChildPath, srcStateMgr, srcAccessMgr, id, flag, refTracker); // store new child node stateMgr.store(newChildState); // add new child node entry to new node newState.addChildNodeEntry(entry.getName(), newChildState.getNodeId()); } // init version history if needed VersionHistoryInfo history = null; if (versionable && flag == COPY) { NodeId copiedFrom = null; if (fullVersionable) { // base version of copied versionable node is reference value of // the histories jcr:copiedFrom property PropertyId propId = new PropertyId(srcState.getNodeId(), NameConstants.JCR_BASEVERSION); PropertyState prop = (PropertyState) srcStateMgr.getItemState(propId); copiedFrom = prop.getValues()[0].getNodeId(); } InternalVersionManager manager = session.getInternalVersionManager(); history = manager.getVersionHistory(session, newState, copiedFrom); } // copy properties for (Name propName : srcState.getPropertyNames()) { Path propPath = PathFactoryImpl.getInstance().create(srcPath, propName, true); PropertyId propId = new PropertyId(srcState.getNodeId(), propName); if (!srcAccessMgr.canRead(propPath, propId)) { continue; } PropertyState srcChildState = (PropertyState) srcStateMgr.getItemState(propId); /** * special handling required for properties with special semantics * (e.g. those defined by mix:referenceable, mix:versionable, * mix:lockable, et.al.) * * todo FIXME delegate to 'node type instance handler' */ QPropertyDefinition def = ent.getApplicablePropertyDef( srcChildState.getName(), srcChildState.getType(), srcChildState.isMultiValued()); if (NameConstants.MIX_LOCKABLE.equals(def.getDeclaringNodeType())) { // skip properties defined by mix:lockable continue; } PropertyState newChildState = copyPropertyState(srcChildState, id, propName, def); if (history != null) { if (fullVersionable) { if (propName.equals(NameConstants.JCR_VERSIONHISTORY)) { // jcr:versionHistory InternalValue value = InternalValue.create( history.getVersionHistoryId()); newChildState.setValues(new InternalValue[] { value }); } else if (propName.equals(NameConstants.JCR_BASEVERSION) || propName.equals(NameConstants.JCR_PREDECESSORS)) { // jcr:baseVersion or jcr:predecessors InternalValue value = InternalValue.create( history.getRootVersionId()); newChildState.setValues(new InternalValue[] { value }); } else if (propName.equals(NameConstants.JCR_ISCHECKEDOUT)) { // jcr:isCheckedOut newChildState.setValues(new InternalValue[]{InternalValue.create(true)}); } } else { // for simple versionable, we just initialize the // version history when we see the jcr:isCheckedOut if (propName.equals(NameConstants.JCR_ISCHECKEDOUT)) { // jcr:isCheckedOut newChildState.setValues(new InternalValue[]{InternalValue.create(true)}); } } } if (newChildState.getType() == PropertyType.REFERENCE || newChildState.getType() == PropertyType.WEAKREFERENCE) { refTracker.processedReference(newChildState); } // store new property stateMgr.store(newChildState); // add new property entry to new node newState.addPropertyName(propName); } return newState; } catch (ItemStateException ise) { String msg = "internal error: failed to copy state of " + srcState.getNodeId(); log.debug(msg); throw new RepositoryException(msg, ise); } } /** * Copies the specified property state. * * @param srcState the property state to copy. * @param parentId the id of the parent node. * @param propName the name of the property. * @param def the definition of the property. * @return a copy of the property state. * @throws RepositoryException if an error occurs while copying. */ private PropertyState copyPropertyState(PropertyState srcState, NodeId parentId, Name propName, QPropertyDefinition def) throws RepositoryException { PropertyState newState = stateMgr.createNew(propName, parentId); newState.setType(srcState.getType()); newState.setMultiValued(srcState.isMultiValued()); InternalValue[] values = srcState.getValues(); if (values != null) { /** * special handling required for properties with special semantics * (e.g. those defined by mix:referenceable, mix:versionable, * mix:lockable, et.al.) * * todo FIXME delegate to 'node type instance handler' */ if (propName.equals(NameConstants.JCR_UUID) && def.getDeclaringNodeType().equals(NameConstants.MIX_REFERENCEABLE)) { // set correct value of jcr:uuid property newState.setValues(new InternalValue[]{InternalValue.create(parentId.toString())}); } else { InternalValue[] newValues = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { newValues[i] = values[i].createCopy(); } newState.setValues(newValues); } } return newState; } /** * Check that the updatable item state manager is in edit mode. * * @throws IllegalStateException if it isn't */ private void checkInEditMode() throws IllegalStateException { if (!stateMgr.inEditMode()) { throw new IllegalStateException("not in edit mode"); } } /** * Determines whether the specified node is shareable, i.e. * whether the mixin type mix:shareable is either * directly assigned or indirectly inherited. * * @param state node state to check * @return true if the specified node is shareable, false otherwise. * @throws RepositoryException if an error occurs */ private boolean isShareable(NodeState state) throws RepositoryException { // shortcut: check some wellknown built-in types first Name primary = state.getNodeTypeName(); Set mixins = state.getMixinTypeNames(); if (mixins.contains(NameConstants.MIX_SHAREABLE)) { return true; } try { NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(NameConstants.MIX_REFERENCEABLE); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + state.getNodeId(); log.debug(msg); throw new RepositoryException(msg, ntce); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.jcr.ItemNotFoundException; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.NodeStateListener; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.name.PathMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of a HierarchyManager that caches paths of * items. */ public class CachingHierarchyManager extends HierarchyManagerImpl implements NodeStateListener { /** * Default upper limit of cached states */ public static final int DEFAULT_UPPER_LIMIT = 10000; private static final int MAX_UPPER_LIMIT = Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.cacheSize", DEFAULT_UPPER_LIMIT); private static final int CACHE_STATISTICS_LOG_INTERVAL_MILLIS = Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.logInterval", 60000); /** * Logger instance */ private static Logger log = LoggerFactory.getLogger(CachingHierarchyManager.class); /** * Mapping of paths to children in the path map */ private final PathMap pathCache = new PathMap(); /** * Mapping of item ids to LRUEntry in the path map */ private final ReferenceMap idCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); /** * Cache monitor object */ private final Object cacheMonitor = new Object(); /** * Upper limit */ private final int upperLimit; /** * Object collecting and logging statistics about the idCache */ private final CacheStatistics idCacheStatistics; /** * Head of LRU */ private LRUEntry head; /** * Tail of LRU */ private LRUEntry tail; /** * Flag indicating whether consistency checking is enabled. */ 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 * @param provider item state manager */ public CachingHierarchyManager(NodeId rootNodeId, ItemStateManager provider) { super(rootNodeId, provider); upperLimit = MAX_UPPER_LIMIT; idCacheStatistics = new CacheStatistics(); if (log.isTraceEnabled()) { log.trace("CachingHierarchyManager initialized. Max cache size = {}", upperLimit, new Exception()); } else { log.debug("CachingHierarchyManager initialized. Max cache size = {}", upperLimit); } } /** * Enable or disable consistency checks in this instance. * * @param enable true to enable consistency checks; * false to disable */ public void enableConsistencyChecks(boolean enable) { this.consistencyCheckEnabled = enable; } //-------------------------------------------------< base class overrides > /** * {@inheritDoc} */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path pathToNode = path; if ((typesAllowed & RETURN_NODE) == 0) { // if we must not return a node, pass parent path // (since we only cache nodes) pathToNode = path.getAncestor(1); } PathMap.Element element = map(pathToNode); if (element == null) { // not even intermediate match: call base class return super.resolvePath(path, typesAllowed); } LRUEntry entry = element.get(); if (element.hasPath(path)) { // exact match: return answer synchronized (cacheMonitor) { entry.touch(); } return entry.getId(); } Path.Element[] elements = path.getElements(); try { return resolvePath(elements, element.getDepth() + 1, entry.getId(), typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node for entry: " + entry.getId() + ", path: " + path.getString(); logItemStateException(msg, e); log.debug(msg); // 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); } /** * {@inheritDoc} */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { if (id.denotesNode()) { cache((NodeId) id, builder.getPath()); } } /** * {@inheritDoc} * * Overridden method tries to find a mapping for the intermediate item * state 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, CycleDetector detector) throws ItemStateException, RepositoryException { if (state.isNode()) { PathMap.Element element = get(state.getId()); if (element != null) { try { Path.Element[] elements = element.getPath().getElements(); for (int i = elements.length - 1; i >= 0; i--) { builder.addFirst(elements[i]); } return; } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + state.getId(); log.debug(msg); throw new RepositoryException(msg, mpe); } } } super.buildPath(builder, state, detector); if (state.isNode()) { try { cache(((NodeState) state).getNodeId(), builder.getPath()); } catch (MalformedPathException mpe) { log.warn("Failed to build path of " + state.getId()); } } } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} * * Overridden method simply checks whether we have an item matching the id * and returns its path, otherwise calls base implementation. */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { try { return element.getPath(); } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } } return super.getPath(id); } /** * {@inheritDoc} */ public Name getName(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getName(); } } return super.getName(id); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getDepth(); } } return super.getDepth(id); } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { PathMap.Element element = get(nodeId); if (element != null) { PathMap.Element child = get(itemId); if (child != null) { return element.isAncestorOf(child); } } } return super.isAncestor(nodeId, itemId); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { if (modified.isNode()) { nodeModified((NodeState) modified); } } /** * {@inheritDoc} * * If path information is cached for modified, this iterates * over all child nodes in the path map, evicting the ones that do not * (longer) exist in the underlying NodeState. */ public void nodeModified(NodeState modified) { synchronized (cacheMonitor) { for (PathMap.Element element : getCachedPaths(modified.getNodeId())) { for (PathMap.Element child : element.getChildren()) { ChildNodeEntry cne = modified.getChildNodeEntry( child.getName(), child.getNormalizedIndex()); if (cne == null) { // Item does not exist, remove evict(child, true); } else { LRUEntry childEntry = child.get(); if (childEntry != null && !cne.getId().equals(childEntry.getId())) { // Different child item, remove evict(child, true); } } } } checkConsistency(); } } private List> getCachedPaths(NodeId id) { // JCR-2720: Handle the root path as a special case if (rootNodeId.equals(id)) { return Collections.singletonList(pathCache.map( PathFactoryImpl.getInstance().getRootPath(), true)); } LRUEntry entry = idCache.get(id); if (entry != null) { return Arrays.asList(entry.getElements()); } else { return Collections.emptyList(); } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { evictAll(destroyed.getId(), true); } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { if (discarded.isTransient() && !discarded.hasOverlayedState() && discarded.getStatus() == ItemState.STATUS_NEW) { // a new node has been discarded -> remove from cache evictAll(discarded.getId(), true); } else if (provider.hasItemState(discarded.getId())) { evictAll(discarded.getId(), false); } else { evictAll(discarded.getId(), true); } } /** * {@inheritDoc} */ public void nodeAdded(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to find item " + state.getNodeId(), e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } 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} * * Iterate over all cached children of this state and verify each * child's position. */ public void nodesReplaced(NodeState state) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(state.getNodeId()); if (entry == null) { return; } for (PathMap.Element parent : entry.getElements()) { HashMap> newChildrenOrder = new HashMap>(); boolean orderChanged = false; for (PathMap.Element child : parent.getChildren()) { LRUEntry childEntry = child.get(); if (childEntry == null) { // Child has no associated UUID information: we're // therefore unable to determine if this child's // position is still accurate and have to assume // the worst and remove it. evict(child, false); } else { NodeId childId = childEntry.getId(); ChildNodeEntry cne = state.getChildNodeEntry(childId); if (cne == null) { // Child no longer in parent node, so remove it evict(child, false); } else { // Put all children into map of new children order // - regardless whether their position changed or // not - as we might need to reorder them later on. Path.Element newNameIndex = PathFactoryImpl.getInstance().createElement( cne.getName(), cne.getIndex()); newChildrenOrder.put(newNameIndex, child); if (!newNameIndex.equals(child.getPathElement())) { orderChanged = true; } } } } if (orderChanged) { /* If at least one child changed its position, reorder */ parent.setChildren(newChildrenOrder); } } checkConsistency(); } } /** * {@inheritDoc} */ public void nodeRemoved(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to get path of " + state.getNodeId(), e); } 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); } } } //------------------------------------------------------< private methods > /** * Return the first cached path that is mapped to given id. * * @param id node id * @return cached element, null if not found */ private PathMap.Element get(ItemId id) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { entry.touch(); return entry.getElements()[0]; } return null; } } /** * Return the nearest cached element in the path map, given a path. * The returned element is guaranteed to have an associated object that * is not null. * * @param path path * @return cached element, null if not found */ private PathMap.Element map(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, false); while (element != null) { LRUEntry entry = element.get(); if (entry != null) { entry.touch(); return element; } element = element.getParent(); } return null; } } /** * Cache an item in the hierarchy given its id and path. * * @param id node id * @param path path to item */ private void cache(NodeId id, Path path) { synchronized (cacheMonitor) { if (isCached(id, path)) { return; } if (idCache.size() >= upperLimit) { idCacheStatistics.log(); /** * Remove least recently used item. Scans the LRU list from * head to tail and removes the first item that has no children. */ LRUEntry entry = head; while (entry != null) { PathMap.Element[] elements = entry.getElements(); int childrenCount = 0; for (int i = 0; i < elements.length; i++) { childrenCount += elements[i].getChildrenCount(); } if (childrenCount == 0) { evictAll(entry.getId(), false); return; } entry = entry.getNext(); } } PathMap.Element element = pathCache.put(path); if (element.get() != null) { if (!id.equals((element.get()).getId())) { log.debug("overwriting PathMap.Element"); } } LRUEntry entry = idCache.get(id); if (entry == null) { entry = new LRUEntry(id, element); idCache.put(id, entry); } else { entry.addElement(element); } element.set(entry); checkConsistency(); } } /** * Return a flag indicating whether a certain node and/or path is cached. * If path is null, check whether the item is * cached at all. If path is not null, * check whether the item is cached with that path. * * @param id item id * @param path path, may be null * @return true if the item is already cached; * false otherwise */ boolean isCached(NodeId id, Path path) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry == null) { return false; } if (path == null) { return true; } PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i].hasPath(path)) { return true; } } return false; } } /** * Return a flag indicating whether a certain path is cached. * * @param path item path * @return true if the item is already cached; * false otherwise */ boolean isCached(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, true); if (element != null) { return element.get() != null; } return false; } } /** * Remove all path mapping for a given item id. Removes the associated * LRUEntry and the PathMap.Element with it. * Indexes of same name sibling elements are shifted! * * @param id item id */ private void evictAll(ItemId id, boolean shift) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { evict(elements[i], shift); } } checkConsistency(); } } /** * Evict path map element from cache. This will traverse all children * of this element and remove the objects associated with them. * Index of same name sibling items are shifted! * * @param element path map element */ private void evict(PathMap.Element element, boolean shift) { // assert: synchronized (cacheMonitor) element.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { LRUEntry entry = element.get(); if (entry.removeElement(element) == 0) { idCache.remove(entry.getId()); entry.remove(); } } }, false); element.remove(shift); } /** * Invoked when a notification about a child node addition has been received. * * @param state node state where child was added * @param path path to child node * @param id child node id * * @throws PathNotFoundException if the path was not found * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeAdded(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element element = null; LRUEntry entry = idCache.get(id); if (entry != null) { // child node already cached: this can have the following // reasons: // 1) node was moved, cached path is outdated // 2) node was cloned, cached path is still valid NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { PathMap.Element[] elements = entry.getElements(); element = elements[0]; for (int i = 0; i < elements.length; i++) { elements[i].remove(); } } } PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent != null) { parent.insert(path.getNameElement()); } if (element != null) { // store remembered element at new position pathCache.put(path, element); } } /** * Invoked when a notification about a child node removal has been received. * * @param state node state * @param path node path * @param id node id * * @throws PathNotFoundException if the path was not found. * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeRemoved(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent == null) { return; } PathMap.Element element = parent.getDescendant(path.getLastElement(), true); if (element != null) { // with SNS, this might evict a child that is NOT the one // having id, check first whether item has // the id passed as argument LRUEntry entry = element.get(); if (entry != null && !entry.getId().equals(id)) { return; } // if item is shareable, remove this path only, otherwise // every path this item has been mapped to NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { evictAll(id, true); } else { evict(element, true); } } else { // element itself is not cached, but removal might cause SNS // index shifting parent.remove(path.getNameElement()); } } /** * Dump contents of path map and elements included to a string. */ public String toString() { final StringBuilder builder = new StringBuilder(); synchronized (cacheMonitor) { pathCache.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { for (int i = 0; i < element.getDepth(); i++) { builder.append("--"); } builder.append(element.getName()); int index = element.getIndex(); if (index != 0 && index != 1) { builder.append('['); builder.append(index); builder.append(']'); } builder.append(" "); builder.append(element.get()); builder.append("\n"); } }, true); } return builder.toString(); } /** * Check consistency. */ private void checkConsistency() throws IllegalStateException { // assert: synchronized (cacheMonitor) if (!consistencyCheckEnabled) { return; } int elementsInCache = 0; Iterator iter = idCache.values().iterator(); while (iter.hasNext()) { LRUEntry entry = iter.next(); elementsInCache += entry.getElements().length; } class PathMapElementCounter implements PathMap.ElementVisitor { int count; public void elementVisited(PathMap.Element element) { LRUEntry mappedEntry = element.get(); LRUEntry cachedEntry = idCache.get(mappedEntry.getId()); if (cachedEntry == null) { String msg = "Path element (" + element + " ) cached in path map, associated id (" + mappedEntry.getId() + ") isn't."; throw new IllegalStateException(msg); } if (cachedEntry != mappedEntry) { String msg = "LRUEntry associated with element (" + element + " ) in path map is not equal to cached LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } PathMap.Element[] elements = cachedEntry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i] == element) { count++; return; } } String msg = "Element (" + element + ") cached in path map, but not in associated LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } } PathMapElementCounter counter = new PathMapElementCounter(); pathCache.traverse(counter, false); if (counter.count != elementsInCache) { String msg = "PathMap element and cached element count don't match (" + counter.count + " != " + elementsInCache + ")"; throw new IllegalStateException(msg); } } /** * 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 { /** * Previous entry */ private LRUEntry previous; /** * Next entry */ private LRUEntry next; /** * Node id */ private final NodeId id; /** * Elements in path map */ private PathMap.Element[] elements; /** * Create a new instance of this class * * @param id node id * @param element the path map element for this entry */ @SuppressWarnings("unchecked") public LRUEntry(NodeId id, PathMap.Element element) { this.id = id; this.elements = new PathMap.Element[] { element }; append(); } /** * Append entry to end of LRU list */ public void append() { if (tail == null) { head = this; tail = this; } else { previous = tail; tail.next = this; tail = this; } } /** * Remove entry from LRU list */ public void remove() { if (previous != null) { previous.next = next; } if (next != null) { next.previous = previous; } if (head == this) { head = next; } if (tail == this) { tail = previous; } previous = null; next = null; } /** * Touch entry. Removes it from its current position in the LRU list * and moves it to the end. */ public void touch() { remove(); append(); } /** * Return next LRU entry * * @return next LRU entry */ public LRUEntry getNext() { return next; } /** * Return node ID * * @return node ID */ public NodeId getId() { return id; } /** * Return elements in path map that are mapped to id. If * this entry is a shareable node or one of its descendant, it can * be reached by more than one path. * * @return element in path map */ public PathMap.Element[] getElements() { return elements; } /** * Add a mapping to some element. */ @SuppressWarnings("unchecked") public void addElement(PathMap.Element element) { PathMap.Element[] tmp = new PathMap.Element[elements.length + 1]; System.arraycopy(elements, 0, tmp, 0, elements.length); tmp[elements.length] = element; elements = tmp; } /** * Remove a mapping to some element from this entry. * * @return number of mappings left */ @SuppressWarnings("unchecked") public int removeElement(PathMap.Element element) { boolean found = false; for (int i = 0; i < elements.length; i++) { if (found) { elements[i - 1] = elements[i]; } else if (elements[i] == element) { found = true; } } if (found) { PathMap.Element[] tmp = new PathMap.Element[elements.length - 1]; System.arraycopy(elements, 0, tmp, 0, tmp.length); elements = tmp; } return elements.length; } /** * {@inheritDoc} */ public String toString() { return id.toString(); } } private final class CacheStatistics { private final String id; private final ReferenceMap cache; private long timeStamp = 0; public CacheStatistics() { this.id = cacheMonitor.toString(); this.cache = idCache; } public void log() { if (log.isDebugEnabled()) { long now = System.currentTimeMillis(); final String msg = "Cache id = {};size = {};max = {}"; if (log.isTraceEnabled()) { log.trace(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } else if (now > timeStamp + CACHE_STATISTICS_LOG_INTERVAL_MILLIS) { timeStamp = now; log.debug(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.security.Principal; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.security.AccessControlException; import javax.security.auth.Subject; import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.core.config.AccessManagerConfig; import org.apache.jackrabbit.core.config.LoginModuleConfig; import org.apache.jackrabbit.core.config.SecurityConfig; import org.apache.jackrabbit.core.config.SecurityManagerConfig; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; import org.apache.jackrabbit.core.config.UserManagerConfig; import org.apache.jackrabbit.core.security.AMContext; 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.GroupPrincipals; import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl; import org.apache.jackrabbit.core.security.principal.PrincipalProvider; import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl; import org.apache.jackrabbit.core.security.user.MembershipCache; import org.apache.jackrabbit.core.security.user.UserManagerImpl; import org.apache.jackrabbit.core.security.user.action.AuthorizableAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The security manager acts as central managing class for all security related * operations on a low-level non-protected level. It manages the * * {@link PrincipalProvider}s * {@link AccessControlProvider}s * {@link WorkspaceAccessManager} * {@link UserManager} * */ public class DefaultSecurityManager implements JackrabbitSecurityManager { /** * the default logger */ private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class); /** * Flag indicating if the security manager was properly initialized. */ private boolean initialized; /** * the repository implementation */ private RepositoryImpl repository; /** * System session. */ private SystemSession systemSession; /** * System user manager. Implementation needed here for the DefaultPrincipalProvider. */ private UserManager systemUserManager; /** * The user id of the administrator. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ADMIN_ID}). */ protected String adminId; /** * The user id of the anonymous user. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ANONYMOUS_ID}). */ protected String anonymousId; /** * Contains the access control providers per workspace. * key = name of the workspace, * value = {@link AccessControlProvider} */ private final Map acProviders = new HashMap(); /** * the AccessControlProviderFactory */ private AccessControlProviderFactory acProviderFactory; /** * the configured WorkspaceAccessManager */ private WorkspaceAccessManager workspaceAccessManager; /** * the principal provider registry */ private PrincipalProviderRegistry principalProviderRegistry; /** * factory for login-context {@see Repository#login()) */ private AuthContextProvider authContextProvider; //------------------------------------------< JackrabbitSecurityManager >--- /** * @see JackrabbitSecurityManager#init(Repository, Session) */ public synchronized void init(Repository repository, Session systemSession) throws RepositoryException { if (initialized) { throw new IllegalStateException("already initialized"); } if (!(repository instanceof RepositoryImpl)) { throw new RepositoryException("RepositoryImpl expected"); } if (!(systemSession instanceof SystemSession)) { throw new RepositoryException("SystemSession expected"); } this.systemSession = (SystemSession) systemSession; this.repository = (RepositoryImpl) repository; SecurityConfig config = this.repository.getConfig().getSecurityConfig(); LoginModuleConfig loginModConf = config.getLoginModuleConfig(); // build AuthContextProvider based on appName + optional LoginModuleConfig authContextProvider = new AuthContextProvider(config.getAppName(), loginModConf); if (authContextProvider.isLocal()) { log.info("init: use Repository Login-Configuration for " + config.getAppName()); } else if (authContextProvider.isJAAS()) { log.info("init: use JAAS login-configuration for " + config.getAppName()); } else { String msg = "Neither JAAS nor RepositoryConfig contained a valid configuration for " + config.getAppName(); log.error(msg); throw new RepositoryException(msg); } Properties[] moduleConfig = authContextProvider.getModuleConfig(); // retrieve default-ids (admin and anonymous) from login-module-configuration. for (Properties props : moduleConfig) { if (props.containsKey(LoginModuleConfig.PARAM_ADMIN_ID)) { adminId = props.getProperty(LoginModuleConfig.PARAM_ADMIN_ID); } if (props.containsKey(LoginModuleConfig.PARAM_ANONYMOUS_ID)) { anonymousId = props.getProperty(LoginModuleConfig.PARAM_ANONYMOUS_ID); } } // fallback: if (adminId == null) { log.debug("No adminID defined in LoginModule/JAAS config -> using default."); adminId = SecurityConstants.ADMIN_ID; } if (anonymousId == null) { log.debug("No anonymousID defined in LoginModule/JAAS config -> using default."); anonymousId = SecurityConstants.ANONYMOUS_ID; } // create the system userManager and make sure the system-users exist. systemUserManager = createUserManager(this.systemSession); createSystemUsers(systemUserManager, this.systemSession, adminId, anonymousId); // init default ac-provider-factory acProviderFactory = new AccessControlProviderFactoryImpl(); acProviderFactory.init(this.systemSession); // create the workspace access manager SecurityManagerConfig smc = config.getSecurityManagerConfig(); if (smc != null && smc.getWorkspaceAccessConfig() != null) { workspaceAccessManager = smc.getWorkspaceAccessConfig().newInstance(WorkspaceAccessManager.class); } else { // fallback -> the default implementation log.debug("No WorkspaceAccessManager configured; using default."); workspaceAccessManager = createDefaultWorkspaceAccessManager(); } workspaceAccessManager.init(this.systemSession); // initialize principal-provider registry // 1) create default PrincipalProvider defaultPP = createDefaultPrincipalProvider(moduleConfig); // 2) create registry instance principalProviderRegistry = new ProviderRegistryImpl(defaultPP); // 3) register all configured principal providers. for (Properties props : moduleConfig) { principalProviderRegistry.registerProvider(props); } initialized = true; } /** * @see JackrabbitSecurityManager#dispose(String) */ public void dispose(String workspaceName) { checkInitialized(); synchronized (acProviders) { AccessControlProvider prov = acProviders.remove(workspaceName); if (prov != null) { prov.close(); } } } /** * @see JackrabbitSecurityManager#close() */ public void close() { checkInitialized(); synchronized (acProviders) { for (AccessControlProvider accessControlProvider : acProviders.values()) { accessControlProvider.close(); } acProviders.clear(); } } /** * @see JackrabbitSecurityManager#getAccessManager(Session,AMContext) */ public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { checkInitialized(); AccessManagerConfig amConfig = repository.getConfig().getSecurityConfig().getAccessManagerConfig(); try { String wspName = session.getWorkspace().getName(); AccessControlProvider pp = getAccessControlProvider(wspName); AccessManager accessMgr; if (amConfig == null) { log.debug("No configuration entry for AccessManager. Using org.apache.jackrabbit.core.security.DefaultAccessManager"); accessMgr = new DefaultAccessManager(); } else { accessMgr = amConfig.newInstance(AccessManager.class); } accessMgr.init(amContext, pp, workspaceAccessManager); return accessMgr; } catch (AccessDeniedException e) { // re-throw throw e; } catch (Exception e) { // wrap in RepositoryException String clsName = (amConfig == null) ? "-- missing access manager configuration --" : amConfig.getClassName(); String msg = "Failed to instantiate AccessManager (" + clsName + ")"; log.error(msg, e); throw new RepositoryException(msg, e); } } /** * @see JackrabbitSecurityManager#getPrincipalManager(Session) */ public PrincipalManager getPrincipalManager(Session session) throws RepositoryException { checkInitialized(); if (session instanceof SessionImpl) { SessionImpl sImpl = (SessionImpl) session; return createPrincipalManager(sImpl); } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserManager(Session) */ public UserManager getUserManager(Session session) throws RepositoryException { checkInitialized(); if (session == systemSession) { return systemUserManager; } else if (session instanceof SessionImpl) { String workspaceName = systemSession.getWorkspace().getName(); try { SessionImpl sImpl = (SessionImpl) session; UserManagerImpl uMgr; if (workspaceName.equals(sImpl.getWorkspace().getName())) { uMgr = createUserManager(sImpl); } else { SessionImpl s = (SessionImpl) sImpl.createSession(workspaceName); uMgr = createUserManager(s); sImpl.addListener(uMgr); } return uMgr; } catch (NoSuchWorkspaceException e) { throw new AccessControlException("Cannot build UserManager for " + session.getUserID(), e); } } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserID(javax.security.auth.Subject, String) */ public String getUserID(Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); // 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(); if (cl != null) { Set s = subject.getPrincipals(cl); if (!s.isEmpty()) { for (Principal p : s) { if (!GroupPrincipals.isGroup(p)) { return p.getName(); } } // all principals found with the given p-Class were Group principals log.debug("Only Group principals found with class '" + cl.getName() + "' -> Not used for UserID."); } else { log.debug("No principal found with class '" + cl.getName() + "'."); } } /* Fallback scenario to retrieve userID from the subject: Since the subject may contain multiple principals and the principal name may not be equals to the UserID, the id is retrieved by searching for the corresponding authorizable and if this doesn't succeed an attempt is made to obtained it from the login-credentials. */ String uid = null; // first try to retrieve an authorizable corresponding to // a non-group principal. the first one present is used // to determine the userID. try { UserManager umgr = getSystemUserManager(workspaceName); for (Principal p : subject.getPrincipals()) { if (!(p instanceof Group)) { Authorizable authorz = umgr.getAuthorizable(p); if (authorz != null && !authorz.isGroup()) { uid = authorz.getID(); break; } } } } catch (RepositoryException e) { // failed to access userid via user manager -> use fallback 2. log.error("Unexpected error while retrieving UserID.", e); } // 2. if no matching user is found try simple access to userID over // SimpleCredentials. if (uid == null) { Iterator creds = subject.getPublicCredentials( SimpleCredentials.class).iterator(); if (creds.hasNext()) { SimpleCredentials sc = creds.next(); uid = sc.getUserID(); } } return uid; } /** * Creates an AuthContext for the given {@link Credentials} and * {@link Subject}. The workspace name is ignored and users are * stored and retrieved from a specific (separate) workspace. * This includes selection of application specific LoginModules and * initialization with credentials and Session to System-Workspace * * @return an {@link AuthContext} for the given Credentials, Subject * @throws RepositoryException in other exceptional repository states */ public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); return getAuthContextProvider().getAuthContext(creds, subject, systemSession, getPrincipalProviderRegistry(), adminId, anonymousId); } //----------------------------------------------------------< protected >--- /** * @return The SecurityManagerConfig configured for the * repository this manager has been created for. */ protected SecurityManagerConfig getConfig() { return repository.getConfig().getSecurityConfig().getSecurityManagerConfig(); } /** * @param workspaceName The name of the target workspace. * @return The system user manager. Since this implementation stores users * in a dedicated workspace the system user manager is the same for all * sessions irrespective of the workspace. * @throws javax.jcr.RepositoryException If an error occurs. */ protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException { return systemUserManager; } /** * @param session The session for which to retrieve the membership cache. * @return The membership cache. * @throws RepositoryException If an error occurs. */ protected MembershipCache getMembershipCache(SessionImpl session) throws RepositoryException { if (session == systemSession || session instanceof SystemSession) { // force creation of the membership cache within the corresponding uMgr return null; } else { return ((UserManagerImpl) getSystemUserManager(session.getWorkspace().getName())).getMembershipCache(); } } /** * Creates a {@link UserManagerImpl} for the given session. May be overridden * to return a custom implementation. * * @param session session * @return user manager * @throws RepositoryException if an error occurs */ protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException { UserManagerConfig umc = getConfig().getUserManagerConfig(); UserManagerImpl um; if (umc != null) { Class>[] paramTypes = new Class[] { SessionImpl.class, String.class, Properties.class, MembershipCache.class}; um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class, paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session)); } else { um = new UserManagerImpl(session, adminId, null, getMembershipCache(session)); } if (umc != null && !(session instanceof SystemSession)) { AuthorizableAction[] actions = umc.getAuthorizableActions(); um.setAuthorizableActions(actions); } return um; } /** * @param session The session used to create the principal manager. * @return A new instance of PrincipalManagerImpl * @throws javax.jcr.RepositoryException If an error occurs. */ protected PrincipalManager createPrincipalManager(SessionImpl session) throws RepositoryException { return new PrincipalManagerImpl(session, getPrincipalProviderRegistry().getProviders()); } /** * @return A nwe instance of WorkspaceAccessManagerImpl to be used as * default workspace access manager if the configuration doesn't specify one. */ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { return new WorkspaceAccessManagerImpl(); } /** * Creates the default principal provider used to create the * {@link PrincipalProviderRegistry}. * * @return An new instance of DefaultPrincipalProvider. * @throws RepositoryException If an error occurs. */ protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { boolean initialized = false; PrincipalProvider defaultPP = new DefaultPrincipalProvider(this.systemSession, (UserManagerImpl) systemUserManager); 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; } /** * @return The PrincipalProviderRegistry created during initialization. */ protected PrincipalProviderRegistry getPrincipalProviderRegistry() { return principalProviderRegistry; } /** * @return The AuthContextProvider created during initialization. */ protected AuthContextProvider getAuthContextProvider() { return authContextProvider; } /** * Throws IllegalStateException if this manager hasn't been * initialized. */ protected void checkInitialized() { if (!initialized) { throw new IllegalStateException("Not initialized"); } } /** * @return The system session used to initialize this SecurityManager. */ protected Session getSystemSession() { return systemSession; } /** * @return The repository used to initialize this SecurityManager. */ protected Repository getRepository() { return repository; } //-------------------------------------------------------------------------- /** * Returns the access control provider for the specified * workspaceName. * * @param workspaceName Name of the workspace. * @return access control provider * @throws NoSuchWorkspaceException If no workspace with 'workspaceName' exists. * @throws RepositoryException */ private AccessControlProvider getAccessControlProvider(String workspaceName) throws NoSuchWorkspaceException, RepositoryException { checkInitialized(); AccessControlProvider provider = acProviders.get(workspaceName); if (provider == null || !provider.isLive()) { // mark this workspace as 'active' so the workspace does not // get disposed by the workspace-janitor // TODO: There should be a cleaner way to do this. repository.markWorkspaceActive(workspaceName); WorkspaceSecurityConfig secConf = null; WorkspaceConfig conf = repository.getConfig().getWorkspaceConfig(workspaceName); if (conf != null) { secConf = conf.getSecurityConfig(); } provider = acProviderFactory.createProvider( repository.getSystemSession(workspaceName), secConf); synchronized (acProviders) { acProviders.put(workspaceName, provider); } } return provider; } /** * Make sure the system users (admin and anonymous) exist. * * @param userManager Manager to create users/groups. * @param session The editing session. * @param adminId UserID of the administrator. * @param anonymousId UserID of the anonymous user. * @throws RepositoryException If an error occurs. */ static void createSystemUsers(UserManager userManager, SystemSession session, String adminId, String anonymousId) throws RepositoryException { Authorizable admin; if (adminId != null) { admin = userManager.getAuthorizable(adminId); if (admin == null) { userManager.createUser(adminId, adminId); if (!userManager.isAutoSave()) { session.save(); } log.info("... created admin-user with id \'" + adminId + "\' ..."); } } if (anonymousId != null) { Authorizable anonymous = userManager.getAuthorizable(anonymousId); if (anonymous == null) { try { userManager.createUser(anonymousId, ""); if (!userManager.isAutoSave()) { session.save(); } log.info("... created anonymous user with id \'" + anonymousId + "\' ..."); } catch (RepositoryException e) { // exception while creating the anonymous user. // log an error but don't abort the repository start-up log.error("Failed to create anonymous user.", e); } } } } //------------------------------------------------------< inner classes >--- /** * WorkspaceAccessManager that upon {@link #grants(Set principals, String)} * evaluates if access to the root node of a workspace with the specified * name is granted. */ private final class WorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { //-----------------------------------------< WorkspaceAccessManager >--- /** * {@inheritDoc} */ public void init(Session systemSession) throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public void close() throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public boolean grants(Set principals, String workspaceName) throws RepositoryException { AccessControlProvider prov = getAccessControlProvider(workspaceName); return prov.canAccessRoot(principals); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ItemNotFoundException; import javax.jcr.RepositoryException; 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.spi.Name; import org.apache.jackrabbit.spi.Path; /** * The HierarchyManager interface ... */ public interface HierarchyManager { /** * Resolves a path into an item id. * * If there is both a node and a property at the specified path, this method * will return the id of the node. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * item to be found at path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #resolveNodePath(Path)} and * {@link #resolvePropertyPath(Path)} should be used instead. * * @param path path to resolve * @return item id referred to by path or null * if there's no item at path. * @throws RepositoryException if an error occurs */ @Deprecated ItemId resolvePath(Path path) throws RepositoryException; /** * Resolves a path into a node id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * node to be found at path. * * @param path path to resolve * @return node id referred to by path or null * if there's no node at path. * @throws RepositoryException if an error occurs */ NodeId resolveNodePath(Path path) throws RepositoryException; /** * Resolves a path into a property id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * property to be found at path. * * @param path path to resolve * @return property id referred to by path or null * if there's no property at path. * @throws RepositoryException if an error occurs */ PropertyId resolvePropertyPath(Path path) throws RepositoryException; /** * Returns the path to the given item. * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item. * @param id id of item whose name should be returned * @return * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item, with the given parent id. If the * given item is not shareable, this is identical to {@link #getName(ItemId)}. * * @param id node id * @param parentId parent node id * @return name * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified item which is equivalent to * getPath(id).getAncestorCount(). The depth reflects the * absolute hierarchy level. * * @param id item id * @return the depth of the specified item * @throws ItemNotFoundException if the specified id does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified descendant relative to the given * ancestor. If ancestorId and descendantId * denote the same item 0 is returned. If ancestorId does not * denote an ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestorId does not * denote an ancestor of the item denoted by descendantId * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; /** * Determines whether the node with the specified nodeId * is an ancestor of the item denoted by the given itemId. * This is equivalent to * getPath(nodeId).isAncestorOf(getPath(itemId)). * * @param nodeId node id * @param itemId item id * @return true if the node with the specified * nodeId is an ancestor of the item denoted by the * given itemId; false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException; //------------------------------------------- operation with shareable nodes /** * Determines whether the node with the specified ancestor * is a share ancestor of the item denoted by the given descendant. * This is true for two nodes A, B * if either: * * A is a (proper) ancestor of B * there is a non-empty sequence of nodes N1,... * ,Nk such that A= * N1 and B=Nk * and Ni is the parent or a share-parent of * Ni+1 (for every i in 1 * ...k-1. * * * @param ancestor node id * @param descendant item id * @return true if the node denoted by ancestor * is a share ancestor of the item denoted by descendant, * false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified share-descendant relative to the given * share-ancestor. If ancestor and descendant * denote the same item, 0 is returned. If ancestor * does not denote an share-ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestor does * not denote a share-ancestor of the item denoted by descendant * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getShareRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; 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.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; 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.PropertyState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HierarchyManagerImpl ... */ public class HierarchyManagerImpl implements HierarchyManager { private static Logger log = LoggerFactory.getLogger(HierarchyManagerImpl.class); /** * The parent name returned for orphaned or root nodes. * TODO: Is it proper to use an invalid Name for this. */ private static final Name EMPTY_NAME = NameFactoryImpl.getInstance().create("", ""); protected final NodeId rootNodeId; protected final ItemStateManager provider; /** * Flags describing what items to return in {@link #resolvePath(Path, int)}. */ static final int RETURN_NODE = 1; static final int RETURN_PROPERTY = 2; static final int RETURN_ANY = (RETURN_NODE | RETURN_PROPERTY); public HierarchyManagerImpl(NodeId rootNodeId, ItemStateManager provider) { this.rootNodeId = rootNodeId; this.provider = provider; } public NodeId getRootNodeId() { return rootNodeId; } //-------------------------------------------------------< implementation > /** * Internal implementation that iteratively resolves a path into an item. * * @param elements path elements * @param next index of next item in elements to inspect * @param id id of item at path elements[0]..elements[next - 1] * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws ItemStateException if an intermediate item state is not found * @throws MalformedPathException if building an intermediate path fails */ protected ItemId resolvePath(Path.Element[] elements, int next, ItemId id, int typesAllowed) throws ItemStateException, MalformedPathException { PathBuilder builder = new PathBuilder(); for (int i = 0; i < next; i++) { builder.addLast(elements[i]); } for (int i = next; i < elements.length; i++) { Path.Element elem = elements[i]; NodeId parentId = (NodeId) id; id = null; Name name = elem.getName(); int index = elem.getIndex(); if (index == 0) { index = 1; } int typeExpected = typesAllowed; if (i < elements.length - 1) { // intermediate items must always be nodes typeExpected = RETURN_NODE; } NodeState parentState = (NodeState) getItemState(parentId); if ((typeExpected & RETURN_NODE) != 0) { ChildNodeEntry nodeEntry = getChildNodeEntry(parentState, name, index); if (nodeEntry != null) { id = nodeEntry.getId(); } } if (id == null && (typeExpected & RETURN_PROPERTY) != 0) { if (parentState.hasPropertyName(name) && (index <= 1)) { // property id = new PropertyId(parentState.getNodeId(), name); } } if (id == null) { break; } builder.addLast(elements[i]); pathResolved(id, builder); } return id; } //---------------------------------------------------------< overridables > /** * Return an item state, given its item id. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return item state * @throws NoSuchItemStateException if the item does not exist * @throws ItemStateException if an error occurs * @see ZombieHierarchyManager#getItemState(ItemId) */ protected ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { return provider.getItemState(id); } /** * Determines whether an item state for a given item id exists. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return true if an item state exists, otherwise * false * @see ZombieHierarchyManager#hasItemState(ItemId) */ protected boolean hasItemState(ItemId id) { return provider.hasItemState(id); } /** * Returns the parentUUID of the given item. * * Low-level hook provided for specialized derived classes. * * @param state item state * @return parentUUID of the given item * @see ZombieHierarchyManager#getParentId(ItemState) */ protected NodeId getParentId(ItemState state) { return state.getParentId(); } /** * Return all parents of a node. A shareable node has possibly more than * one parent. * * @param state item state * @param useOverlayed whether to use overlayed state for shareable nodes * @return set of parent NodeIds. If state has no parent, * array has length 0. */ protected Set getParentIds(ItemState state, boolean useOverlayed) { if (state.isNode()) { // if this is a node, quickly check whether it is shareable and // whether it contains more than one parent NodeState ns = (NodeState) state; if (ns.isShareable() && useOverlayed && ns.hasOverlayedState()) { ns = (NodeState) ns.getOverlayedState(); } Set s = ns.getSharedSet(); if (s.size() > 1) { return s; } } NodeId parentId = getParentId(state); if (parentId != null) { LinkedHashSet s = new LinkedHashSet(); s.add(parentId); return s; } return Collections.emptySet(); } /** * Returns the ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
* Precondition: the state manager needs to be in edit mode. * * @param parent * @param propName * @param type * @param numValues * @return * @throws ItemExistsException * @throws ConstraintViolationException * @throws RepositoryException * @throws IllegalStateException if the state manager is not in edit mode */ public PropertyState createPropertyState(NodeState parent, Name propName, int type, int numValues) throws ItemExistsException, ConstraintViolationException, RepositoryException, IllegalStateException { // check precondition if (!stateMgr.inEditMode()) { throw new IllegalStateException( "cannot create property state for " + propName + " because manager is not in edit mode"); } // find applicable definition QPropertyDefinition def; // multi- or single-valued property? if (numValues == 1) { // could be single- or multi-valued (n == 1) try { // try single-valued def = findApplicablePropertyDefinition(propName, type, false, parent); } catch (ConstraintViolationException cve) { // try multi-valued def = findApplicablePropertyDefinition(propName, type, true, parent); } } else { // can only be multi-valued (n == 0 || n > 1) def = findApplicablePropertyDefinition(propName, type, true, parent); } return createPropertyState(parent, propName, type, def); } /** * Creates a new property based on the given definition. *
* Precondition: the state manager needs to be in edit mode. * * @param parent * @param propName * @param type * @param def * @return * @throws ItemExistsException * @throws RepositoryException */ public PropertyState createPropertyState(NodeState parent, Name propName, int type, QPropertyDefinition def) throws ItemExistsException, RepositoryException { // check for name collisions with existing properties if (parent.hasPropertyName(propName)) { PropertyId errorId = new PropertyId(parent.getNodeId(), propName); throw new ItemExistsException(safeGetJCRPath(errorId)); } // create property PropertyState prop = stateMgr.createNew(propName, parent.getNodeId()); if (def.getRequiredType() != PropertyType.UNDEFINED) { prop.setType(def.getRequiredType()); } else if (type != PropertyType.UNDEFINED) { prop.setType(type); } else { prop.setType(PropertyType.STRING); } prop.setMultiValued(def.isMultiple()); // compute system generated values if necessary new NodeTypeInstanceHandler(session.getUserID()).setDefaultValues( prop, parent, def); // now add new property entry to parent parent.addPropertyName(propName); // store parent stateMgr.store(parent); return prop; } /** * Unlinks the specified node state from its parent and recursively * removes it including its properties and child nodes. *
* 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 * subject to the following checks: access rights, locking, versioning. * * @param target * @throws RepositoryException if an error occurs */ public void removeNodeState(NodeState target) throws RepositoryException { NodeId parentId = target.getParentId(); if (parentId == null) { String msg = "root node cannot be removed"; log.debug(msg); throw new RepositoryException(msg); } // remove target recursiveRemoveNodeState(target); // remove child node entry from parent NodeState parent = getNodeState(parentId); parent.removeChildNodeEntry(target.getNodeId()); // store parent stateMgr.store(parent); } /** * Retrieves the state of the node at the given path. *
* Note that access rights are not enforced! * * @param nodePath * @return * @throws PathNotFoundException * @throws RepositoryException */ public NodeState getNodeState(Path nodePath) throws PathNotFoundException, RepositoryException { return getNodeState(stateMgr, hierMgr, nodePath); } /** * Retrieves the state of the node with the given id. *
* Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public NodeState getNodeState(NodeId id) throws ItemNotFoundException, RepositoryException { return (NodeState) getItemState(stateMgr, id); } /** * Retrieves the state of the property with the given id. *
* Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public PropertyState getPropertyState(PropertyId id) throws ItemNotFoundException, RepositoryException { return (PropertyState) getItemState(stateMgr, id); } /** * Retrieves the state of the item with the given id. *
* Note that access rights are not enforced! * * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ public ItemState getItemState(ItemId id) throws ItemNotFoundException, RepositoryException { return getItemState(stateMgr, id); } //----------------------------------------------------< protected methods > /** * Verifies that the node at nodePath is checked-out; throws a * VersionException if that's not the case. *
VersionException
* A node is considered checked-out 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 * ancestors. * * @param nodePath * @throws PathNotFoundException * @throws VersionException * @throws RepositoryException */ protected void verifyCheckedOut(Path nodePath) throws PathNotFoundException, VersionException, RepositoryException { // search nearest ancestor that is versionable, start with node at nodePath /** * FIXME should not only rely on existence of jcr:isCheckedOut property * but also verify that node.isNodeType("mix:versionable")==true; * this would have a negative impact on performance though... */ NodeState nodeState = getNodeState(nodePath); while (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { if (nodePath.denotesRoot()) { return; } nodePath = nodePath.getAncestor(1); nodeState = getNodeState(nodePath); } PropertyId propId = new PropertyId(nodeState.getNodeId(), NameConstants.JCR_ISCHECKEDOUT); PropertyState propState; try { propState = (PropertyState) stateMgr.getItemState(propId); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + safeGetJCRPath(propId); log.debug(msg); throw new RepositoryException(msg, ise); } boolean checkedOut = propState.getValues()[0].getBoolean(); if (!checkedOut) { throw new VersionException(safeGetJCRPath(nodePath) + " is checked-in"); } } /** * Verifies that the node at nodePath is not locked by * somebody else than the current session. * * @param nodePath path of node to check * @throws PathNotFoundException * @throws LockException if write access to the specified path is not allowed * @throws RepositoryException if another error occurs */ protected void verifyUnlocked(Path nodePath) throws LockException, RepositoryException { // make sure there's no foreign lock on node at nodePath context.getWorkspace().getInternalLockManager().checkLock( nodePath, session); } /** * Verifies that the node at nodePath is not protected. * * @param nodePath path of node to check * @throws PathNotFoundException if no node exists at nodePath * @throws ConstraintViolationException if write access to the specified * path is not allowed * @throws RepositoryException if another error occurs */ protected void verifyNotProtected(Path nodePath) throws PathNotFoundException, ConstraintViolationException, RepositoryException { NodeState node = getNodeState(nodePath); if (context.getItemManager().getDefinition(node).isProtected()) { throw new ConstraintViolationException(safeGetJCRPath(nodePath) + ": node is protected"); } } /** * Retrieves the state of the node at nodePath using the given * item state manager. *
* Note that access rights are not enforced! * * @param srcStateMgr * @param srcHierMgr * @param nodePath * @return * @throws PathNotFoundException * @throws RepositoryException */ protected NodeState getNodeState(ItemStateManager srcStateMgr, HierarchyManager srcHierMgr, Path nodePath) throws PathNotFoundException, RepositoryException { try { NodeId id = srcHierMgr.resolveNodePath(nodePath); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(nodePath)); } return (NodeState) getItemState(srcStateMgr, id); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(nodePath)); } } /** * Retrieves the state of the item with the specified id using the given * item state manager. *
* Note that access rights are not enforced! * * @param srcStateMgr * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ protected ItemState getItemState(ItemStateManager srcStateMgr, ItemId id) throws ItemNotFoundException, RepositoryException { try { return srcStateMgr.getItemState(id); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(safeGetJCRPath(id)); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + safeGetJCRPath(id); log.debug(msg); throw new RepositoryException(msg, ise); } } //------------------------------------------------------< private methods > /** * Recursively removes the given node state including its properties and * child nodes. *
* The removal of child nodes is subject to the following checks: * access rights, locking & versioning status. Referential integrity * (references) is checked on commit. *
* Note that the child node entry refering to targetState is * not automatically removed from targetState's * parent. * * @param targetState * @throws RepositoryException if an error occurs */ private void recursiveRemoveNodeState(NodeState targetState) throws RepositoryException { if (targetState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(targetState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); NodeId nodeId = entry.getId(); try { NodeState nodeState = (NodeState) stateMgr.getItemState(nodeId); // check if child node can be removed // (access rights, locking & versioning status as well // as retention and hold); // referential integrity (references) is checked // on commit checkRemoveNode(nodeState, targetState.getNodeId(), CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_HOLD | CHECK_RETENTION ); // remove child node recursiveRemoveNodeState(nodeState); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } // remove child node entry targetState.removeChildNodeEntry(entry.getName(), entry.getIndex()); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(targetState.getPropertyNames()); for (Name propName : tmp) { PropertyId propId = new PropertyId(targetState.getNodeId(), propName); try { PropertyState propState = (PropertyState) stateMgr.getItemState(propId); // remove property entry targetState.removePropertyName(propId.getName()); // destroy property state stateMgr.destroy(propState); } catch (ItemStateException ise) { String msg = "internal error: failed to retrieve state of " + propId; log.debug(msg); throw new RepositoryException(msg, ise); } } // now actually do unlink target state targetState.setParentId(null); // destroy target state (pass overlayed state since target state // might have been modified during unlinking) stateMgr.destroy(targetState.getOverlayedState()); } /** * Recursively copies the specified node state including its properties and * child nodes. * * @param srcState * @param srcPath * @param srcStateMgr * @param srcAccessMgr * @param destParentId * @param flag one of * * COPY * CLONE * CLONE_REMOVE_EXISTING * * @param refTracker tracks uuid mappings and processed reference properties * @return a deep copy of the given node state and its children * @throws RepositoryException if an error occurs */ private NodeState copyNodeState(NodeState srcState, Path srcPath, ItemStateManager srcStateMgr, AccessManager srcAccessMgr, NodeId destParentId, int flag, ReferenceChangeTracker refTracker) throws RepositoryException { NodeState newState; try { NodeId id = null; EffectiveNodeType ent = getEffectiveNodeType(srcState); boolean referenceable = ent.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean versionable = ent.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE); boolean fullVersionable = ent.includesNodeType(NameConstants.MIX_VERSIONABLE); boolean shareable = ent.includesNodeType(NameConstants.MIX_SHAREABLE); switch (flag) { case COPY: /* if this node is shareable and another node in the same shared set * has been already been copied and given a new uuid, use this one * (see section 14.5 of the specification) */ if (shareable && refTracker.getMappedId(srcState.getNodeId()) != null) { NodeId newId = refTracker.getMappedId(srcState.getNodeId()); NodeState sharedState = (NodeState) stateMgr.getItemState(newId); sharedState.addShare(destParentId); return sharedState; } break; case CLONE: if (!referenceable) { // non-referenceable node: always create new node id break; } // use same uuid as source node id = srcState.getNodeId(); if (stateMgr.hasItemState(id)) { if (shareable) { NodeState sharedState = (NodeState) stateMgr.getItemState(id); sharedState.addShare(destParentId); return sharedState; } // node with this uuid already exists throw new ItemExistsException(safeGetJCRPath(id)); } break; case CLONE_REMOVE_EXISTING: if (!referenceable) { // non-referenceable node: always create new node id break; } // use same uuid as source node id = srcState.getNodeId(); if (stateMgr.hasItemState(id)) { NodeState existingState = (NodeState) stateMgr.getItemState(id); // make sure existing node is not the parent // or an ancestor thereof if (id.equals(destParentId) || hierMgr.isAncestor(id, destParentId)) { String msg = "cannot remove node " + safeGetJCRPath(srcPath) + " because it is an ancestor of the destination"; log.debug(msg); throw new RepositoryException(msg); } // check if existing can be removed // (access rights, locking & versioning status, // node type constraints and retention/hold) checkRemoveNode(existingState, CHECK_ACCESS | CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION); // do remove existing removeNodeState(existingState); } break; default: throw new IllegalArgumentException( "unknown flag for copying node state: " + flag); } newState = stateMgr.createNew(id, srcState.getNodeTypeName(), destParentId); id = newState.getNodeId(); if (flag == COPY && referenceable) { // remember uuid mapping refTracker.mappedId(srcState.getNodeId(), id); } // copy node state newState.setMixinTypeNames(srcState.getMixinTypeNames()); if (shareable) { // initialize shared set newState.addShare(destParentId); } // copy child nodes for (ChildNodeEntry entry : srcState.getChildNodeEntries()) { Path srcChildPath = PathFactoryImpl.getInstance().create(srcPath, entry.getName(), true); if (!srcAccessMgr.isGranted(srcChildPath, Permission.READ)) { continue; } NodeId nodeId = entry.getId(); NodeState srcChildState = (NodeState) srcStateMgr.getItemState(nodeId); /** * special handling required for child nodes with special semantics * (e.g. those defined by nt:version, et.al.) * * todo FIXME delegate to 'node type instance handler' */ /** * If child is shareble and its UUID has already been remapped, * then simply add a reference to the state with that remapped * UUID instead of copying the whole subtree. */ if (srcChildState.isShareable()) { NodeId mappedId = refTracker.getMappedId(srcChildState.getNodeId()); if (mappedId != null) { if (stateMgr.hasItemState(mappedId)) { NodeState destState = (NodeState) stateMgr.getItemState(mappedId); if (!destState.isShareable()) { String msg = "Remapped child (" + safeGetJCRPath(srcPath) + ") is not shareable."; throw new ItemStateException(msg); } if (!destState.addShare(id)) { String msg = "Unable to add share to node: " + id; throw new ItemStateException(msg); } stateMgr.store(destState); newState.addChildNodeEntry(entry.getName(), mappedId); continue; } } } // recursive copying of child node NodeState newChildState = copyNodeState(srcChildState, srcChildPath, srcStateMgr, srcAccessMgr, id, flag, refTracker); // store new child node stateMgr.store(newChildState); // add new child node entry to new node newState.addChildNodeEntry(entry.getName(), newChildState.getNodeId()); } // init version history if needed VersionHistoryInfo history = null; if (versionable && flag == COPY) { NodeId copiedFrom = null; if (fullVersionable) { // base version of copied versionable node is reference value of // the histories jcr:copiedFrom property PropertyId propId = new PropertyId(srcState.getNodeId(), NameConstants.JCR_BASEVERSION); PropertyState prop = (PropertyState) srcStateMgr.getItemState(propId); copiedFrom = prop.getValues()[0].getNodeId(); } InternalVersionManager manager = session.getInternalVersionManager(); history = manager.getVersionHistory(session, newState, copiedFrom); } // copy properties for (Name propName : srcState.getPropertyNames()) { Path propPath = PathFactoryImpl.getInstance().create(srcPath, propName, true); PropertyId propId = new PropertyId(srcState.getNodeId(), propName); if (!srcAccessMgr.canRead(propPath, propId)) { continue; } PropertyState srcChildState = (PropertyState) srcStateMgr.getItemState(propId); /** * special handling required for properties with special semantics * (e.g. those defined by mix:referenceable, mix:versionable, * mix:lockable, et.al.) * * todo FIXME delegate to 'node type instance handler' */ QPropertyDefinition def = ent.getApplicablePropertyDef( srcChildState.getName(), srcChildState.getType(), srcChildState.isMultiValued()); if (NameConstants.MIX_LOCKABLE.equals(def.getDeclaringNodeType())) { // skip properties defined by mix:lockable continue; } PropertyState newChildState = copyPropertyState(srcChildState, id, propName, def); if (history != null) { if (fullVersionable) { if (propName.equals(NameConstants.JCR_VERSIONHISTORY)) { // jcr:versionHistory InternalValue value = InternalValue.create( history.getVersionHistoryId()); newChildState.setValues(new InternalValue[] { value }); } else if (propName.equals(NameConstants.JCR_BASEVERSION) || propName.equals(NameConstants.JCR_PREDECESSORS)) { // jcr:baseVersion or jcr:predecessors InternalValue value = InternalValue.create( history.getRootVersionId()); newChildState.setValues(new InternalValue[] { value }); } else if (propName.equals(NameConstants.JCR_ISCHECKEDOUT)) { // jcr:isCheckedOut newChildState.setValues(new InternalValue[]{InternalValue.create(true)}); } } else { // for simple versionable, we just initialize the // version history when we see the jcr:isCheckedOut if (propName.equals(NameConstants.JCR_ISCHECKEDOUT)) { // jcr:isCheckedOut newChildState.setValues(new InternalValue[]{InternalValue.create(true)}); } } } if (newChildState.getType() == PropertyType.REFERENCE || newChildState.getType() == PropertyType.WEAKREFERENCE) { refTracker.processedReference(newChildState); } // store new property stateMgr.store(newChildState); // add new property entry to new node newState.addPropertyName(propName); } return newState; } catch (ItemStateException ise) { String msg = "internal error: failed to copy state of " + srcState.getNodeId(); log.debug(msg); throw new RepositoryException(msg, ise); } } /** * Copies the specified property state. * * @param srcState the property state to copy. * @param parentId the id of the parent node. * @param propName the name of the property. * @param def the definition of the property. * @return a copy of the property state. * @throws RepositoryException if an error occurs while copying. */ private PropertyState copyPropertyState(PropertyState srcState, NodeId parentId, Name propName, QPropertyDefinition def) throws RepositoryException { PropertyState newState = stateMgr.createNew(propName, parentId); newState.setType(srcState.getType()); newState.setMultiValued(srcState.isMultiValued()); InternalValue[] values = srcState.getValues(); if (values != null) { /** * special handling required for properties with special semantics * (e.g. those defined by mix:referenceable, mix:versionable, * mix:lockable, et.al.) * * todo FIXME delegate to 'node type instance handler' */ if (propName.equals(NameConstants.JCR_UUID) && def.getDeclaringNodeType().equals(NameConstants.MIX_REFERENCEABLE)) { // set correct value of jcr:uuid property newState.setValues(new InternalValue[]{InternalValue.create(parentId.toString())}); } else { InternalValue[] newValues = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { newValues[i] = values[i].createCopy(); } newState.setValues(newValues); } } return newState; } /** * Check that the updatable item state manager is in edit mode. * * @throws IllegalStateException if it isn't */ private void checkInEditMode() throws IllegalStateException { if (!stateMgr.inEditMode()) { throw new IllegalStateException("not in edit mode"); } } /** * Determines whether the specified node is shareable, i.e. * whether the mixin type mix:shareable is either * directly assigned or indirectly inherited. * * @param state node state to check * @return true if the specified node is shareable, false otherwise. * @throws RepositoryException if an error occurs */ private boolean isShareable(NodeState state) throws RepositoryException { // shortcut: check some wellknown built-in types first Name primary = state.getNodeTypeName(); Set mixins = state.getMixinTypeNames(); if (mixins.contains(NameConstants.MIX_SHAREABLE)) { return true; } try { NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(NameConstants.MIX_REFERENCEABLE); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + state.getNodeId(); log.debug(msg); throw new RepositoryException(msg, ntce); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.jcr.ItemNotFoundException; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.NodeStateListener; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.name.PathMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of a HierarchyManager that caches paths of * items. */ public class CachingHierarchyManager extends HierarchyManagerImpl implements NodeStateListener { /** * Default upper limit of cached states */ public static final int DEFAULT_UPPER_LIMIT = 10000; private static final int MAX_UPPER_LIMIT = Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.cacheSize", DEFAULT_UPPER_LIMIT); private static final int CACHE_STATISTICS_LOG_INTERVAL_MILLIS = Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.logInterval", 60000); /** * Logger instance */ private static Logger log = LoggerFactory.getLogger(CachingHierarchyManager.class); /** * Mapping of paths to children in the path map */ private final PathMap pathCache = new PathMap(); /** * Mapping of item ids to LRUEntry in the path map */ private final ReferenceMap idCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); /** * Cache monitor object */ private final Object cacheMonitor = new Object(); /** * Upper limit */ private final int upperLimit; /** * Object collecting and logging statistics about the idCache */ private final CacheStatistics idCacheStatistics; /** * Head of LRU */ private LRUEntry head; /** * Tail of LRU */ private LRUEntry tail; /** * Flag indicating whether consistency checking is enabled. */ 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 * @param provider item state manager */ public CachingHierarchyManager(NodeId rootNodeId, ItemStateManager provider) { super(rootNodeId, provider); upperLimit = MAX_UPPER_LIMIT; idCacheStatistics = new CacheStatistics(); if (log.isTraceEnabled()) { log.trace("CachingHierarchyManager initialized. Max cache size = {}", upperLimit, new Exception()); } else { log.debug("CachingHierarchyManager initialized. Max cache size = {}", upperLimit); } } /** * Enable or disable consistency checks in this instance. * * @param enable true to enable consistency checks; * false to disable */ public void enableConsistencyChecks(boolean enable) { this.consistencyCheckEnabled = enable; } //-------------------------------------------------< base class overrides > /** * {@inheritDoc} */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path pathToNode = path; if ((typesAllowed & RETURN_NODE) == 0) { // if we must not return a node, pass parent path // (since we only cache nodes) pathToNode = path.getAncestor(1); } PathMap.Element element = map(pathToNode); if (element == null) { // not even intermediate match: call base class return super.resolvePath(path, typesAllowed); } LRUEntry entry = element.get(); if (element.hasPath(path)) { // exact match: return answer synchronized (cacheMonitor) { entry.touch(); } return entry.getId(); } Path.Element[] elements = path.getElements(); try { return resolvePath(elements, element.getDepth() + 1, entry.getId(), typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node for entry: " + entry.getId() + ", path: " + path.getString(); logItemStateException(msg, e); log.debug(msg); // 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); } /** * {@inheritDoc} */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { if (id.denotesNode()) { cache((NodeId) id, builder.getPath()); } } /** * {@inheritDoc} * * Overridden method tries to find a mapping for the intermediate item * state 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, CycleDetector detector) throws ItemStateException, RepositoryException { if (state.isNode()) { PathMap.Element element = get(state.getId()); if (element != null) { try { Path.Element[] elements = element.getPath().getElements(); for (int i = elements.length - 1; i >= 0; i--) { builder.addFirst(elements[i]); } return; } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + state.getId(); log.debug(msg); throw new RepositoryException(msg, mpe); } } } super.buildPath(builder, state, detector); if (state.isNode()) { try { cache(((NodeState) state).getNodeId(), builder.getPath()); } catch (MalformedPathException mpe) { log.warn("Failed to build path of " + state.getId()); } } } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} * * Overridden method simply checks whether we have an item matching the id * and returns its path, otherwise calls base implementation. */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { try { return element.getPath(); } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } } return super.getPath(id); } /** * {@inheritDoc} */ public Name getName(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getName(); } } return super.getName(id); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getDepth(); } } return super.getDepth(id); } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { PathMap.Element element = get(nodeId); if (element != null) { PathMap.Element child = get(itemId); if (child != null) { return element.isAncestorOf(child); } } } return super.isAncestor(nodeId, itemId); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { if (modified.isNode()) { nodeModified((NodeState) modified); } } /** * {@inheritDoc} * * If path information is cached for modified, this iterates * over all child nodes in the path map, evicting the ones that do not * (longer) exist in the underlying NodeState. */ public void nodeModified(NodeState modified) { synchronized (cacheMonitor) { for (PathMap.Element element : getCachedPaths(modified.getNodeId())) { for (PathMap.Element child : element.getChildren()) { ChildNodeEntry cne = modified.getChildNodeEntry( child.getName(), child.getNormalizedIndex()); if (cne == null) { // Item does not exist, remove evict(child, true); } else { LRUEntry childEntry = child.get(); if (childEntry != null && !cne.getId().equals(childEntry.getId())) { // Different child item, remove evict(child, true); } } } } checkConsistency(); } } private List> getCachedPaths(NodeId id) { // JCR-2720: Handle the root path as a special case if (rootNodeId.equals(id)) { return Collections.singletonList(pathCache.map( PathFactoryImpl.getInstance().getRootPath(), true)); } LRUEntry entry = idCache.get(id); if (entry != null) { return Arrays.asList(entry.getElements()); } else { return Collections.emptyList(); } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { evictAll(destroyed.getId(), true); } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { if (discarded.isTransient() && !discarded.hasOverlayedState() && discarded.getStatus() == ItemState.STATUS_NEW) { // a new node has been discarded -> remove from cache evictAll(discarded.getId(), true); } else if (provider.hasItemState(discarded.getId())) { evictAll(discarded.getId(), false); } else { evictAll(discarded.getId(), true); } } /** * {@inheritDoc} */ public void nodeAdded(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to find item " + state.getNodeId(), e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } 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} * * Iterate over all cached children of this state and verify each * child's position. */ public void nodesReplaced(NodeState state) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(state.getNodeId()); if (entry == null) { return; } for (PathMap.Element parent : entry.getElements()) { HashMap> newChildrenOrder = new HashMap>(); boolean orderChanged = false; for (PathMap.Element child : parent.getChildren()) { LRUEntry childEntry = child.get(); if (childEntry == null) { // Child has no associated UUID information: we're // therefore unable to determine if this child's // position is still accurate and have to assume // the worst and remove it. evict(child, false); } else { NodeId childId = childEntry.getId(); ChildNodeEntry cne = state.getChildNodeEntry(childId); if (cne == null) { // Child no longer in parent node, so remove it evict(child, false); } else { // Put all children into map of new children order // - regardless whether their position changed or // not - as we might need to reorder them later on. Path.Element newNameIndex = PathFactoryImpl.getInstance().createElement( cne.getName(), cne.getIndex()); newChildrenOrder.put(newNameIndex, child); if (!newNameIndex.equals(child.getPathElement())) { orderChanged = true; } } } } if (orderChanged) { /* If at least one child changed its position, reorder */ parent.setChildren(newChildrenOrder); } } checkConsistency(); } } /** * {@inheritDoc} */ public void nodeRemoved(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to get path of " + state.getNodeId(), e); } 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); } } } //------------------------------------------------------< private methods > /** * Return the first cached path that is mapped to given id. * * @param id node id * @return cached element, null if not found */ private PathMap.Element get(ItemId id) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { entry.touch(); return entry.getElements()[0]; } return null; } } /** * Return the nearest cached element in the path map, given a path. * The returned element is guaranteed to have an associated object that * is not null. * * @param path path * @return cached element, null if not found */ private PathMap.Element map(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, false); while (element != null) { LRUEntry entry = element.get(); if (entry != null) { entry.touch(); return element; } element = element.getParent(); } return null; } } /** * Cache an item in the hierarchy given its id and path. * * @param id node id * @param path path to item */ private void cache(NodeId id, Path path) { synchronized (cacheMonitor) { if (isCached(id, path)) { return; } if (idCache.size() >= upperLimit) { idCacheStatistics.log(); /** * Remove least recently used item. Scans the LRU list from * head to tail and removes the first item that has no children. */ LRUEntry entry = head; while (entry != null) { PathMap.Element[] elements = entry.getElements(); int childrenCount = 0; for (int i = 0; i < elements.length; i++) { childrenCount += elements[i].getChildrenCount(); } if (childrenCount == 0) { evictAll(entry.getId(), false); return; } entry = entry.getNext(); } } PathMap.Element element = pathCache.put(path); if (element.get() != null) { if (!id.equals((element.get()).getId())) { log.debug("overwriting PathMap.Element"); } } LRUEntry entry = idCache.get(id); if (entry == null) { entry = new LRUEntry(id, element); idCache.put(id, entry); } else { entry.addElement(element); } element.set(entry); checkConsistency(); } } /** * Return a flag indicating whether a certain node and/or path is cached. * If path is null, check whether the item is * cached at all. If path is not null, * check whether the item is cached with that path. * * @param id item id * @param path path, may be null * @return true if the item is already cached; * false otherwise */ boolean isCached(NodeId id, Path path) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry == null) { return false; } if (path == null) { return true; } PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i].hasPath(path)) { return true; } } return false; } } /** * Return a flag indicating whether a certain path is cached. * * @param path item path * @return true if the item is already cached; * false otherwise */ boolean isCached(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, true); if (element != null) { return element.get() != null; } return false; } } /** * Remove all path mapping for a given item id. Removes the associated * LRUEntry and the PathMap.Element with it. * Indexes of same name sibling elements are shifted! * * @param id item id */ private void evictAll(ItemId id, boolean shift) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { evict(elements[i], shift); } } checkConsistency(); } } /** * Evict path map element from cache. This will traverse all children * of this element and remove the objects associated with them. * Index of same name sibling items are shifted! * * @param element path map element */ private void evict(PathMap.Element element, boolean shift) { // assert: synchronized (cacheMonitor) element.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { LRUEntry entry = element.get(); if (entry.removeElement(element) == 0) { idCache.remove(entry.getId()); entry.remove(); } } }, false); element.remove(shift); } /** * Invoked when a notification about a child node addition has been received. * * @param state node state where child was added * @param path path to child node * @param id child node id * * @throws PathNotFoundException if the path was not found * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeAdded(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element element = null; LRUEntry entry = idCache.get(id); if (entry != null) { // child node already cached: this can have the following // reasons: // 1) node was moved, cached path is outdated // 2) node was cloned, cached path is still valid NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { PathMap.Element[] elements = entry.getElements(); element = elements[0]; for (int i = 0; i < elements.length; i++) { elements[i].remove(); } } } PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent != null) { parent.insert(path.getNameElement()); } if (element != null) { // store remembered element at new position pathCache.put(path, element); } } /** * Invoked when a notification about a child node removal has been received. * * @param state node state * @param path node path * @param id node id * * @throws PathNotFoundException if the path was not found. * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeRemoved(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent == null) { return; } PathMap.Element element = parent.getDescendant(path.getLastElement(), true); if (element != null) { // with SNS, this might evict a child that is NOT the one // having id, check first whether item has // the id passed as argument LRUEntry entry = element.get(); if (entry != null && !entry.getId().equals(id)) { return; } // if item is shareable, remove this path only, otherwise // every path this item has been mapped to NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { evictAll(id, true); } else { evict(element, true); } } else { // element itself is not cached, but removal might cause SNS // index shifting parent.remove(path.getNameElement()); } } /** * Dump contents of path map and elements included to a string. */ public String toString() { final StringBuilder builder = new StringBuilder(); synchronized (cacheMonitor) { pathCache.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { for (int i = 0; i < element.getDepth(); i++) { builder.append("--"); } builder.append(element.getName()); int index = element.getIndex(); if (index != 0 && index != 1) { builder.append('['); builder.append(index); builder.append(']'); } builder.append(" "); builder.append(element.get()); builder.append("\n"); } }, true); } return builder.toString(); } /** * Check consistency. */ private void checkConsistency() throws IllegalStateException { // assert: synchronized (cacheMonitor) if (!consistencyCheckEnabled) { return; } int elementsInCache = 0; Iterator iter = idCache.values().iterator(); while (iter.hasNext()) { LRUEntry entry = iter.next(); elementsInCache += entry.getElements().length; } class PathMapElementCounter implements PathMap.ElementVisitor { int count; public void elementVisited(PathMap.Element element) { LRUEntry mappedEntry = element.get(); LRUEntry cachedEntry = idCache.get(mappedEntry.getId()); if (cachedEntry == null) { String msg = "Path element (" + element + " ) cached in path map, associated id (" + mappedEntry.getId() + ") isn't."; throw new IllegalStateException(msg); } if (cachedEntry != mappedEntry) { String msg = "LRUEntry associated with element (" + element + " ) in path map is not equal to cached LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } PathMap.Element[] elements = cachedEntry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i] == element) { count++; return; } } String msg = "Element (" + element + ") cached in path map, but not in associated LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } } PathMapElementCounter counter = new PathMapElementCounter(); pathCache.traverse(counter, false); if (counter.count != elementsInCache) { String msg = "PathMap element and cached element count don't match (" + counter.count + " != " + elementsInCache + ")"; throw new IllegalStateException(msg); } } /** * 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 { /** * Previous entry */ private LRUEntry previous; /** * Next entry */ private LRUEntry next; /** * Node id */ private final NodeId id; /** * Elements in path map */ private PathMap.Element[] elements; /** * Create a new instance of this class * * @param id node id * @param element the path map element for this entry */ @SuppressWarnings("unchecked") public LRUEntry(NodeId id, PathMap.Element element) { this.id = id; this.elements = new PathMap.Element[] { element }; append(); } /** * Append entry to end of LRU list */ public void append() { if (tail == null) { head = this; tail = this; } else { previous = tail; tail.next = this; tail = this; } } /** * Remove entry from LRU list */ public void remove() { if (previous != null) { previous.next = next; } if (next != null) { next.previous = previous; } if (head == this) { head = next; } if (tail == this) { tail = previous; } previous = null; next = null; } /** * Touch entry. Removes it from its current position in the LRU list * and moves it to the end. */ public void touch() { remove(); append(); } /** * Return next LRU entry * * @return next LRU entry */ public LRUEntry getNext() { return next; } /** * Return node ID * * @return node ID */ public NodeId getId() { return id; } /** * Return elements in path map that are mapped to id. If * this entry is a shareable node or one of its descendant, it can * be reached by more than one path. * * @return element in path map */ public PathMap.Element[] getElements() { return elements; } /** * Add a mapping to some element. */ @SuppressWarnings("unchecked") public void addElement(PathMap.Element element) { PathMap.Element[] tmp = new PathMap.Element[elements.length + 1]; System.arraycopy(elements, 0, tmp, 0, elements.length); tmp[elements.length] = element; elements = tmp; } /** * Remove a mapping to some element from this entry. * * @return number of mappings left */ @SuppressWarnings("unchecked") public int removeElement(PathMap.Element element) { boolean found = false; for (int i = 0; i < elements.length; i++) { if (found) { elements[i - 1] = elements[i]; } else if (elements[i] == element) { found = true; } } if (found) { PathMap.Element[] tmp = new PathMap.Element[elements.length - 1]; System.arraycopy(elements, 0, tmp, 0, tmp.length); elements = tmp; } return elements.length; } /** * {@inheritDoc} */ public String toString() { return id.toString(); } } private final class CacheStatistics { private final String id; private final ReferenceMap cache; private long timeStamp = 0; public CacheStatistics() { this.id = cacheMonitor.toString(); this.cache = idCache; } public void log() { if (log.isDebugEnabled()) { long now = System.currentTimeMillis(); final String msg = "Cache id = {};size = {};max = {}"; if (log.isTraceEnabled()) { log.trace(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } else if (now > timeStamp + CACHE_STATISTICS_LOG_INTERVAL_MILLIS) { timeStamp = now; log.debug(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.security.Principal; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.security.AccessControlException; import javax.security.auth.Subject; import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.core.config.AccessManagerConfig; import org.apache.jackrabbit.core.config.LoginModuleConfig; import org.apache.jackrabbit.core.config.SecurityConfig; import org.apache.jackrabbit.core.config.SecurityManagerConfig; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; import org.apache.jackrabbit.core.config.UserManagerConfig; import org.apache.jackrabbit.core.security.AMContext; 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.GroupPrincipals; import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl; import org.apache.jackrabbit.core.security.principal.PrincipalProvider; import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl; import org.apache.jackrabbit.core.security.user.MembershipCache; import org.apache.jackrabbit.core.security.user.UserManagerImpl; import org.apache.jackrabbit.core.security.user.action.AuthorizableAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The security manager acts as central managing class for all security related * operations on a low-level non-protected level. It manages the * * {@link PrincipalProvider}s * {@link AccessControlProvider}s * {@link WorkspaceAccessManager} * {@link UserManager} * */ public class DefaultSecurityManager implements JackrabbitSecurityManager { /** * the default logger */ private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class); /** * Flag indicating if the security manager was properly initialized. */ private boolean initialized; /** * the repository implementation */ private RepositoryImpl repository; /** * System session. */ private SystemSession systemSession; /** * System user manager. Implementation needed here for the DefaultPrincipalProvider. */ private UserManager systemUserManager; /** * The user id of the administrator. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ADMIN_ID}). */ protected String adminId; /** * The user id of the anonymous user. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ANONYMOUS_ID}). */ protected String anonymousId; /** * Contains the access control providers per workspace. * key = name of the workspace, * value = {@link AccessControlProvider} */ private final Map acProviders = new HashMap(); /** * the AccessControlProviderFactory */ private AccessControlProviderFactory acProviderFactory; /** * the configured WorkspaceAccessManager */ private WorkspaceAccessManager workspaceAccessManager; /** * the principal provider registry */ private PrincipalProviderRegistry principalProviderRegistry; /** * factory for login-context {@see Repository#login()) */ private AuthContextProvider authContextProvider; //------------------------------------------< JackrabbitSecurityManager >--- /** * @see JackrabbitSecurityManager#init(Repository, Session) */ public synchronized void init(Repository repository, Session systemSession) throws RepositoryException { if (initialized) { throw new IllegalStateException("already initialized"); } if (!(repository instanceof RepositoryImpl)) { throw new RepositoryException("RepositoryImpl expected"); } if (!(systemSession instanceof SystemSession)) { throw new RepositoryException("SystemSession expected"); } this.systemSession = (SystemSession) systemSession; this.repository = (RepositoryImpl) repository; SecurityConfig config = this.repository.getConfig().getSecurityConfig(); LoginModuleConfig loginModConf = config.getLoginModuleConfig(); // build AuthContextProvider based on appName + optional LoginModuleConfig authContextProvider = new AuthContextProvider(config.getAppName(), loginModConf); if (authContextProvider.isLocal()) { log.info("init: use Repository Login-Configuration for " + config.getAppName()); } else if (authContextProvider.isJAAS()) { log.info("init: use JAAS login-configuration for " + config.getAppName()); } else { String msg = "Neither JAAS nor RepositoryConfig contained a valid configuration for " + config.getAppName(); log.error(msg); throw new RepositoryException(msg); } Properties[] moduleConfig = authContextProvider.getModuleConfig(); // retrieve default-ids (admin and anonymous) from login-module-configuration. for (Properties props : moduleConfig) { if (props.containsKey(LoginModuleConfig.PARAM_ADMIN_ID)) { adminId = props.getProperty(LoginModuleConfig.PARAM_ADMIN_ID); } if (props.containsKey(LoginModuleConfig.PARAM_ANONYMOUS_ID)) { anonymousId = props.getProperty(LoginModuleConfig.PARAM_ANONYMOUS_ID); } } // fallback: if (adminId == null) { log.debug("No adminID defined in LoginModule/JAAS config -> using default."); adminId = SecurityConstants.ADMIN_ID; } if (anonymousId == null) { log.debug("No anonymousID defined in LoginModule/JAAS config -> using default."); anonymousId = SecurityConstants.ANONYMOUS_ID; } // create the system userManager and make sure the system-users exist. systemUserManager = createUserManager(this.systemSession); createSystemUsers(systemUserManager, this.systemSession, adminId, anonymousId); // init default ac-provider-factory acProviderFactory = new AccessControlProviderFactoryImpl(); acProviderFactory.init(this.systemSession); // create the workspace access manager SecurityManagerConfig smc = config.getSecurityManagerConfig(); if (smc != null && smc.getWorkspaceAccessConfig() != null) { workspaceAccessManager = smc.getWorkspaceAccessConfig().newInstance(WorkspaceAccessManager.class); } else { // fallback -> the default implementation log.debug("No WorkspaceAccessManager configured; using default."); workspaceAccessManager = createDefaultWorkspaceAccessManager(); } workspaceAccessManager.init(this.systemSession); // initialize principal-provider registry // 1) create default PrincipalProvider defaultPP = createDefaultPrincipalProvider(moduleConfig); // 2) create registry instance principalProviderRegistry = new ProviderRegistryImpl(defaultPP); // 3) register all configured principal providers. for (Properties props : moduleConfig) { principalProviderRegistry.registerProvider(props); } initialized = true; } /** * @see JackrabbitSecurityManager#dispose(String) */ public void dispose(String workspaceName) { checkInitialized(); synchronized (acProviders) { AccessControlProvider prov = acProviders.remove(workspaceName); if (prov != null) { prov.close(); } } } /** * @see JackrabbitSecurityManager#close() */ public void close() { checkInitialized(); synchronized (acProviders) { for (AccessControlProvider accessControlProvider : acProviders.values()) { accessControlProvider.close(); } acProviders.clear(); } } /** * @see JackrabbitSecurityManager#getAccessManager(Session,AMContext) */ public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { checkInitialized(); AccessManagerConfig amConfig = repository.getConfig().getSecurityConfig().getAccessManagerConfig(); try { String wspName = session.getWorkspace().getName(); AccessControlProvider pp = getAccessControlProvider(wspName); AccessManager accessMgr; if (amConfig == null) { log.debug("No configuration entry for AccessManager. Using org.apache.jackrabbit.core.security.DefaultAccessManager"); accessMgr = new DefaultAccessManager(); } else { accessMgr = amConfig.newInstance(AccessManager.class); } accessMgr.init(amContext, pp, workspaceAccessManager); return accessMgr; } catch (AccessDeniedException e) { // re-throw throw e; } catch (Exception e) { // wrap in RepositoryException String clsName = (amConfig == null) ? "-- missing access manager configuration --" : amConfig.getClassName(); String msg = "Failed to instantiate AccessManager (" + clsName + ")"; log.error(msg, e); throw new RepositoryException(msg, e); } } /** * @see JackrabbitSecurityManager#getPrincipalManager(Session) */ public PrincipalManager getPrincipalManager(Session session) throws RepositoryException { checkInitialized(); if (session instanceof SessionImpl) { SessionImpl sImpl = (SessionImpl) session; return createPrincipalManager(sImpl); } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserManager(Session) */ public UserManager getUserManager(Session session) throws RepositoryException { checkInitialized(); if (session == systemSession) { return systemUserManager; } else if (session instanceof SessionImpl) { String workspaceName = systemSession.getWorkspace().getName(); try { SessionImpl sImpl = (SessionImpl) session; UserManagerImpl uMgr; if (workspaceName.equals(sImpl.getWorkspace().getName())) { uMgr = createUserManager(sImpl); } else { SessionImpl s = (SessionImpl) sImpl.createSession(workspaceName); uMgr = createUserManager(s); sImpl.addListener(uMgr); } return uMgr; } catch (NoSuchWorkspaceException e) { throw new AccessControlException("Cannot build UserManager for " + session.getUserID(), e); } } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserID(javax.security.auth.Subject, String) */ public String getUserID(Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); // 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(); if (cl != null) { Set s = subject.getPrincipals(cl); if (!s.isEmpty()) { for (Principal p : s) { if (!GroupPrincipals.isGroup(p)) { return p.getName(); } } // all principals found with the given p-Class were Group principals log.debug("Only Group principals found with class '" + cl.getName() + "' -> Not used for UserID."); } else { log.debug("No principal found with class '" + cl.getName() + "'."); } } /* Fallback scenario to retrieve userID from the subject: Since the subject may contain multiple principals and the principal name may not be equals to the UserID, the id is retrieved by searching for the corresponding authorizable and if this doesn't succeed an attempt is made to obtained it from the login-credentials. */ String uid = null; // first try to retrieve an authorizable corresponding to // a non-group principal. the first one present is used // to determine the userID. try { UserManager umgr = getSystemUserManager(workspaceName); for (Principal p : subject.getPrincipals()) { if (!(p instanceof Group)) { Authorizable authorz = umgr.getAuthorizable(p); if (authorz != null && !authorz.isGroup()) { uid = authorz.getID(); break; } } } } catch (RepositoryException e) { // failed to access userid via user manager -> use fallback 2. log.error("Unexpected error while retrieving UserID.", e); } // 2. if no matching user is found try simple access to userID over // SimpleCredentials. if (uid == null) { Iterator creds = subject.getPublicCredentials( SimpleCredentials.class).iterator(); if (creds.hasNext()) { SimpleCredentials sc = creds.next(); uid = sc.getUserID(); } } return uid; } /** * Creates an AuthContext for the given {@link Credentials} and * {@link Subject}. The workspace name is ignored and users are * stored and retrieved from a specific (separate) workspace. * This includes selection of application specific LoginModules and * initialization with credentials and Session to System-Workspace * * @return an {@link AuthContext} for the given Credentials, Subject * @throws RepositoryException in other exceptional repository states */ public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); return getAuthContextProvider().getAuthContext(creds, subject, systemSession, getPrincipalProviderRegistry(), adminId, anonymousId); } //----------------------------------------------------------< protected >--- /** * @return The SecurityManagerConfig configured for the * repository this manager has been created for. */ protected SecurityManagerConfig getConfig() { return repository.getConfig().getSecurityConfig().getSecurityManagerConfig(); } /** * @param workspaceName The name of the target workspace. * @return The system user manager. Since this implementation stores users * in a dedicated workspace the system user manager is the same for all * sessions irrespective of the workspace. * @throws javax.jcr.RepositoryException If an error occurs. */ protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException { return systemUserManager; } /** * @param session The session for which to retrieve the membership cache. * @return The membership cache. * @throws RepositoryException If an error occurs. */ protected MembershipCache getMembershipCache(SessionImpl session) throws RepositoryException { if (session == systemSession || session instanceof SystemSession) { // force creation of the membership cache within the corresponding uMgr return null; } else { return ((UserManagerImpl) getSystemUserManager(session.getWorkspace().getName())).getMembershipCache(); } } /** * Creates a {@link UserManagerImpl} for the given session. May be overridden * to return a custom implementation. * * @param session session * @return user manager * @throws RepositoryException if an error occurs */ protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException { UserManagerConfig umc = getConfig().getUserManagerConfig(); UserManagerImpl um; if (umc != null) { Class>[] paramTypes = new Class[] { SessionImpl.class, String.class, Properties.class, MembershipCache.class}; um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class, paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session)); } else { um = new UserManagerImpl(session, adminId, null, getMembershipCache(session)); } if (umc != null && !(session instanceof SystemSession)) { AuthorizableAction[] actions = umc.getAuthorizableActions(); um.setAuthorizableActions(actions); } return um; } /** * @param session The session used to create the principal manager. * @return A new instance of PrincipalManagerImpl * @throws javax.jcr.RepositoryException If an error occurs. */ protected PrincipalManager createPrincipalManager(SessionImpl session) throws RepositoryException { return new PrincipalManagerImpl(session, getPrincipalProviderRegistry().getProviders()); } /** * @return A nwe instance of WorkspaceAccessManagerImpl to be used as * default workspace access manager if the configuration doesn't specify one. */ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { return new WorkspaceAccessManagerImpl(); } /** * Creates the default principal provider used to create the * {@link PrincipalProviderRegistry}. * * @return An new instance of DefaultPrincipalProvider. * @throws RepositoryException If an error occurs. */ protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { boolean initialized = false; PrincipalProvider defaultPP = new DefaultPrincipalProvider(this.systemSession, (UserManagerImpl) systemUserManager); 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; } /** * @return The PrincipalProviderRegistry created during initialization. */ protected PrincipalProviderRegistry getPrincipalProviderRegistry() { return principalProviderRegistry; } /** * @return The AuthContextProvider created during initialization. */ protected AuthContextProvider getAuthContextProvider() { return authContextProvider; } /** * Throws IllegalStateException if this manager hasn't been * initialized. */ protected void checkInitialized() { if (!initialized) { throw new IllegalStateException("Not initialized"); } } /** * @return The system session used to initialize this SecurityManager. */ protected Session getSystemSession() { return systemSession; } /** * @return The repository used to initialize this SecurityManager. */ protected Repository getRepository() { return repository; } //-------------------------------------------------------------------------- /** * Returns the access control provider for the specified * workspaceName. * * @param workspaceName Name of the workspace. * @return access control provider * @throws NoSuchWorkspaceException If no workspace with 'workspaceName' exists. * @throws RepositoryException */ private AccessControlProvider getAccessControlProvider(String workspaceName) throws NoSuchWorkspaceException, RepositoryException { checkInitialized(); AccessControlProvider provider = acProviders.get(workspaceName); if (provider == null || !provider.isLive()) { // mark this workspace as 'active' so the workspace does not // get disposed by the workspace-janitor // TODO: There should be a cleaner way to do this. repository.markWorkspaceActive(workspaceName); WorkspaceSecurityConfig secConf = null; WorkspaceConfig conf = repository.getConfig().getWorkspaceConfig(workspaceName); if (conf != null) { secConf = conf.getSecurityConfig(); } provider = acProviderFactory.createProvider( repository.getSystemSession(workspaceName), secConf); synchronized (acProviders) { acProviders.put(workspaceName, provider); } } return provider; } /** * Make sure the system users (admin and anonymous) exist. * * @param userManager Manager to create users/groups. * @param session The editing session. * @param adminId UserID of the administrator. * @param anonymousId UserID of the anonymous user. * @throws RepositoryException If an error occurs. */ static void createSystemUsers(UserManager userManager, SystemSession session, String adminId, String anonymousId) throws RepositoryException { Authorizable admin; if (adminId != null) { admin = userManager.getAuthorizable(adminId); if (admin == null) { userManager.createUser(adminId, adminId); if (!userManager.isAutoSave()) { session.save(); } log.info("... created admin-user with id \'" + adminId + "\' ..."); } } if (anonymousId != null) { Authorizable anonymous = userManager.getAuthorizable(anonymousId); if (anonymous == null) { try { userManager.createUser(anonymousId, ""); if (!userManager.isAutoSave()) { session.save(); } log.info("... created anonymous user with id \'" + anonymousId + "\' ..."); } catch (RepositoryException e) { // exception while creating the anonymous user. // log an error but don't abort the repository start-up log.error("Failed to create anonymous user.", e); } } } } //------------------------------------------------------< inner classes >--- /** * WorkspaceAccessManager that upon {@link #grants(Set principals, String)} * evaluates if access to the root node of a workspace with the specified * name is granted. */ private final class WorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { //-----------------------------------------< WorkspaceAccessManager >--- /** * {@inheritDoc} */ public void init(Session systemSession) throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public void close() throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public boolean grants(Set principals, String workspaceName) throws RepositoryException { AccessControlProvider prov = getAccessControlProvider(workspaceName); return prov.canAccessRoot(principals); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ItemNotFoundException; import javax.jcr.RepositoryException; 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.spi.Name; import org.apache.jackrabbit.spi.Path; /** * The HierarchyManager interface ... */ public interface HierarchyManager { /** * Resolves a path into an item id. * * If there is both a node and a property at the specified path, this method * will return the id of the node. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * item to be found at path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #resolveNodePath(Path)} and * {@link #resolvePropertyPath(Path)} should be used instead. * * @param path path to resolve * @return item id referred to by path or null * if there's no item at path. * @throws RepositoryException if an error occurs */ @Deprecated ItemId resolvePath(Path path) throws RepositoryException; /** * Resolves a path into a node id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * node to be found at path. * * @param path path to resolve * @return node id referred to by path or null * if there's no node at path. * @throws RepositoryException if an error occurs */ NodeId resolveNodePath(Path path) throws RepositoryException; /** * Resolves a path into a property id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * property to be found at path. * * @param path path to resolve * @return property id referred to by path or null * if there's no property at path. * @throws RepositoryException if an error occurs */ PropertyId resolvePropertyPath(Path path) throws RepositoryException; /** * Returns the path to the given item. * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item. * @param id id of item whose name should be returned * @return * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item, with the given parent id. If the * given item is not shareable, this is identical to {@link #getName(ItemId)}. * * @param id node id * @param parentId parent node id * @return name * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified item which is equivalent to * getPath(id).getAncestorCount(). The depth reflects the * absolute hierarchy level. * * @param id item id * @return the depth of the specified item * @throws ItemNotFoundException if the specified id does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified descendant relative to the given * ancestor. If ancestorId and descendantId * denote the same item 0 is returned. If ancestorId does not * denote an ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestorId does not * denote an ancestor of the item denoted by descendantId * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; /** * Determines whether the node with the specified nodeId * is an ancestor of the item denoted by the given itemId. * This is equivalent to * getPath(nodeId).isAncestorOf(getPath(itemId)). * * @param nodeId node id * @param itemId item id * @return true if the node with the specified * nodeId is an ancestor of the item denoted by the * given itemId; false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException; //------------------------------------------- operation with shareable nodes /** * Determines whether the node with the specified ancestor * is a share ancestor of the item denoted by the given descendant. * This is true for two nodes A, B * if either: * * A is a (proper) ancestor of B * there is a non-empty sequence of nodes N1,... * ,Nk such that A= * N1 and B=Nk * and Ni is the parent or a share-parent of * Ni+1 (for every i in 1 * ...k-1. * * * @param ancestor node id * @param descendant item id * @return true if the node denoted by ancestor * is a share ancestor of the item denoted by descendant, * false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified share-descendant relative to the given * share-ancestor. If ancestor and descendant * denote the same item, 0 is returned. If ancestor * does not denote an share-ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestor does * not denote a share-ancestor of the item denoted by descendant * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getShareRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; 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.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; 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.PropertyState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HierarchyManagerImpl ... */ public class HierarchyManagerImpl implements HierarchyManager { private static Logger log = LoggerFactory.getLogger(HierarchyManagerImpl.class); /** * The parent name returned for orphaned or root nodes. * TODO: Is it proper to use an invalid Name for this. */ private static final Name EMPTY_NAME = NameFactoryImpl.getInstance().create("", ""); protected final NodeId rootNodeId; protected final ItemStateManager provider; /** * Flags describing what items to return in {@link #resolvePath(Path, int)}. */ static final int RETURN_NODE = 1; static final int RETURN_PROPERTY = 2; static final int RETURN_ANY = (RETURN_NODE | RETURN_PROPERTY); public HierarchyManagerImpl(NodeId rootNodeId, ItemStateManager provider) { this.rootNodeId = rootNodeId; this.provider = provider; } public NodeId getRootNodeId() { return rootNodeId; } //-------------------------------------------------------< implementation > /** * Internal implementation that iteratively resolves a path into an item. * * @param elements path elements * @param next index of next item in elements to inspect * @param id id of item at path elements[0]..elements[next - 1] * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws ItemStateException if an intermediate item state is not found * @throws MalformedPathException if building an intermediate path fails */ protected ItemId resolvePath(Path.Element[] elements, int next, ItemId id, int typesAllowed) throws ItemStateException, MalformedPathException { PathBuilder builder = new PathBuilder(); for (int i = 0; i < next; i++) { builder.addLast(elements[i]); } for (int i = next; i < elements.length; i++) { Path.Element elem = elements[i]; NodeId parentId = (NodeId) id; id = null; Name name = elem.getName(); int index = elem.getIndex(); if (index == 0) { index = 1; } int typeExpected = typesAllowed; if (i < elements.length - 1) { // intermediate items must always be nodes typeExpected = RETURN_NODE; } NodeState parentState = (NodeState) getItemState(parentId); if ((typeExpected & RETURN_NODE) != 0) { ChildNodeEntry nodeEntry = getChildNodeEntry(parentState, name, index); if (nodeEntry != null) { id = nodeEntry.getId(); } } if (id == null && (typeExpected & RETURN_PROPERTY) != 0) { if (parentState.hasPropertyName(name) && (index <= 1)) { // property id = new PropertyId(parentState.getNodeId(), name); } } if (id == null) { break; } builder.addLast(elements[i]); pathResolved(id, builder); } return id; } //---------------------------------------------------------< overridables > /** * Return an item state, given its item id. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return item state * @throws NoSuchItemStateException if the item does not exist * @throws ItemStateException if an error occurs * @see ZombieHierarchyManager#getItemState(ItemId) */ protected ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { return provider.getItemState(id); } /** * Determines whether an item state for a given item id exists. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return true if an item state exists, otherwise * false * @see ZombieHierarchyManager#hasItemState(ItemId) */ protected boolean hasItemState(ItemId id) { return provider.hasItemState(id); } /** * Returns the parentUUID of the given item. * * Low-level hook provided for specialized derived classes. * * @param state item state * @return parentUUID of the given item * @see ZombieHierarchyManager#getParentId(ItemState) */ protected NodeId getParentId(ItemState state) { return state.getParentId(); } /** * Return all parents of a node. A shareable node has possibly more than * one parent. * * @param state item state * @param useOverlayed whether to use overlayed state for shareable nodes * @return set of parent NodeIds. If state has no parent, * array has length 0. */ protected Set getParentIds(ItemState state, boolean useOverlayed) { if (state.isNode()) { // if this is a node, quickly check whether it is shareable and // whether it contains more than one parent NodeState ns = (NodeState) state; if (ns.isShareable() && useOverlayed && ns.hasOverlayedState()) { ns = (NodeState) ns.getOverlayedState(); } Set s = ns.getSharedSet(); if (s.size() > 1) { return s; } } NodeId parentId = getParentId(state); if (parentId != null) { LinkedHashSet s = new LinkedHashSet(); s.add(parentId); return s; } return Collections.emptySet(); } /** * Returns the ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
targetState
HierarchyManager
LRUEntry
false
* Overridden method tries to find a mapping for the intermediate item * state 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, CycleDetector detector) throws ItemStateException, RepositoryException { if (state.isNode()) { PathMap.Element element = get(state.getId()); if (element != null) { try { Path.Element[] elements = element.getPath().getElements(); for (int i = elements.length - 1; i >= 0; i--) { builder.addFirst(elements[i]); } return; } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + state.getId(); log.debug(msg); throw new RepositoryException(msg, mpe); } } } super.buildPath(builder, state, detector); if (state.isNode()) { try { cache(((NodeState) state).getNodeId(), builder.getPath()); } catch (MalformedPathException mpe) { log.warn("Failed to build path of " + state.getId()); } } } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} * * Overridden method simply checks whether we have an item matching the id * and returns its path, otherwise calls base implementation. */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { try { return element.getPath(); } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } } return super.getPath(id); } /** * {@inheritDoc} */ public Name getName(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getName(); } } return super.getName(id); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getDepth(); } } return super.getDepth(id); } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { PathMap.Element element = get(nodeId); if (element != null) { PathMap.Element child = get(itemId); if (child != null) { return element.isAncestorOf(child); } } } return super.isAncestor(nodeId, itemId); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { if (modified.isNode()) { nodeModified((NodeState) modified); } } /** * {@inheritDoc} * * If path information is cached for modified, this iterates * over all child nodes in the path map, evicting the ones that do not * (longer) exist in the underlying NodeState. */ public void nodeModified(NodeState modified) { synchronized (cacheMonitor) { for (PathMap.Element element : getCachedPaths(modified.getNodeId())) { for (PathMap.Element child : element.getChildren()) { ChildNodeEntry cne = modified.getChildNodeEntry( child.getName(), child.getNormalizedIndex()); if (cne == null) { // Item does not exist, remove evict(child, true); } else { LRUEntry childEntry = child.get(); if (childEntry != null && !cne.getId().equals(childEntry.getId())) { // Different child item, remove evict(child, true); } } } } checkConsistency(); } } private List> getCachedPaths(NodeId id) { // JCR-2720: Handle the root path as a special case if (rootNodeId.equals(id)) { return Collections.singletonList(pathCache.map( PathFactoryImpl.getInstance().getRootPath(), true)); } LRUEntry entry = idCache.get(id); if (entry != null) { return Arrays.asList(entry.getElements()); } else { return Collections.emptyList(); } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { evictAll(destroyed.getId(), true); } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { if (discarded.isTransient() && !discarded.hasOverlayedState() && discarded.getStatus() == ItemState.STATUS_NEW) { // a new node has been discarded -> remove from cache evictAll(discarded.getId(), true); } else if (provider.hasItemState(discarded.getId())) { evictAll(discarded.getId(), false); } else { evictAll(discarded.getId(), true); } } /** * {@inheritDoc} */ public void nodeAdded(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to find item " + state.getNodeId(), e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } 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} * * Iterate over all cached children of this state and verify each * child's position. */ public void nodesReplaced(NodeState state) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(state.getNodeId()); if (entry == null) { return; } for (PathMap.Element parent : entry.getElements()) { HashMap> newChildrenOrder = new HashMap>(); boolean orderChanged = false; for (PathMap.Element child : parent.getChildren()) { LRUEntry childEntry = child.get(); if (childEntry == null) { // Child has no associated UUID information: we're // therefore unable to determine if this child's // position is still accurate and have to assume // the worst and remove it. evict(child, false); } else { NodeId childId = childEntry.getId(); ChildNodeEntry cne = state.getChildNodeEntry(childId); if (cne == null) { // Child no longer in parent node, so remove it evict(child, false); } else { // Put all children into map of new children order // - regardless whether their position changed or // not - as we might need to reorder them later on. Path.Element newNameIndex = PathFactoryImpl.getInstance().createElement( cne.getName(), cne.getIndex()); newChildrenOrder.put(newNameIndex, child); if (!newNameIndex.equals(child.getPathElement())) { orderChanged = true; } } } } if (orderChanged) { /* If at least one child changed its position, reorder */ parent.setChildren(newChildrenOrder); } } checkConsistency(); } } /** * {@inheritDoc} */ public void nodeRemoved(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to get path of " + state.getNodeId(), e); } 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); } } } //------------------------------------------------------< private methods > /** * Return the first cached path that is mapped to given id. * * @param id node id * @return cached element, null if not found */ private PathMap.Element get(ItemId id) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { entry.touch(); return entry.getElements()[0]; } return null; } } /** * Return the nearest cached element in the path map, given a path. * The returned element is guaranteed to have an associated object that * is not null. * * @param path path * @return cached element, null if not found */ private PathMap.Element map(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, false); while (element != null) { LRUEntry entry = element.get(); if (entry != null) { entry.touch(); return element; } element = element.getParent(); } return null; } } /** * Cache an item in the hierarchy given its id and path. * * @param id node id * @param path path to item */ private void cache(NodeId id, Path path) { synchronized (cacheMonitor) { if (isCached(id, path)) { return; } if (idCache.size() >= upperLimit) { idCacheStatistics.log(); /** * Remove least recently used item. Scans the LRU list from * head to tail and removes the first item that has no children. */ LRUEntry entry = head; while (entry != null) { PathMap.Element[] elements = entry.getElements(); int childrenCount = 0; for (int i = 0; i < elements.length; i++) { childrenCount += elements[i].getChildrenCount(); } if (childrenCount == 0) { evictAll(entry.getId(), false); return; } entry = entry.getNext(); } } PathMap.Element element = pathCache.put(path); if (element.get() != null) { if (!id.equals((element.get()).getId())) { log.debug("overwriting PathMap.Element"); } } LRUEntry entry = idCache.get(id); if (entry == null) { entry = new LRUEntry(id, element); idCache.put(id, entry); } else { entry.addElement(element); } element.set(entry); checkConsistency(); } } /** * Return a flag indicating whether a certain node and/or path is cached. * If path is null, check whether the item is * cached at all. If path is not null, * check whether the item is cached with that path. * * @param id item id * @param path path, may be null * @return true if the item is already cached; * false otherwise */ boolean isCached(NodeId id, Path path) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry == null) { return false; } if (path == null) { return true; } PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i].hasPath(path)) { return true; } } return false; } } /** * Return a flag indicating whether a certain path is cached. * * @param path item path * @return true if the item is already cached; * false otherwise */ boolean isCached(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, true); if (element != null) { return element.get() != null; } return false; } } /** * Remove all path mapping for a given item id. Removes the associated * LRUEntry and the PathMap.Element with it. * Indexes of same name sibling elements are shifted! * * @param id item id */ private void evictAll(ItemId id, boolean shift) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { evict(elements[i], shift); } } checkConsistency(); } } /** * Evict path map element from cache. This will traverse all children * of this element and remove the objects associated with them. * Index of same name sibling items are shifted! * * @param element path map element */ private void evict(PathMap.Element element, boolean shift) { // assert: synchronized (cacheMonitor) element.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { LRUEntry entry = element.get(); if (entry.removeElement(element) == 0) { idCache.remove(entry.getId()); entry.remove(); } } }, false); element.remove(shift); } /** * Invoked when a notification about a child node addition has been received. * * @param state node state where child was added * @param path path to child node * @param id child node id * * @throws PathNotFoundException if the path was not found * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeAdded(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element element = null; LRUEntry entry = idCache.get(id); if (entry != null) { // child node already cached: this can have the following // reasons: // 1) node was moved, cached path is outdated // 2) node was cloned, cached path is still valid NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { PathMap.Element[] elements = entry.getElements(); element = elements[0]; for (int i = 0; i < elements.length; i++) { elements[i].remove(); } } } PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent != null) { parent.insert(path.getNameElement()); } if (element != null) { // store remembered element at new position pathCache.put(path, element); } } /** * Invoked when a notification about a child node removal has been received. * * @param state node state * @param path node path * @param id node id * * @throws PathNotFoundException if the path was not found. * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeRemoved(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent == null) { return; } PathMap.Element element = parent.getDescendant(path.getLastElement(), true); if (element != null) { // with SNS, this might evict a child that is NOT the one // having id, check first whether item has // the id passed as argument LRUEntry entry = element.get(); if (entry != null && !entry.getId().equals(id)) { return; } // if item is shareable, remove this path only, otherwise // every path this item has been mapped to NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { evictAll(id, true); } else { evict(element, true); } } else { // element itself is not cached, but removal might cause SNS // index shifting parent.remove(path.getNameElement()); } } /** * Dump contents of path map and elements included to a string. */ public String toString() { final StringBuilder builder = new StringBuilder(); synchronized (cacheMonitor) { pathCache.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { for (int i = 0; i < element.getDepth(); i++) { builder.append("--"); } builder.append(element.getName()); int index = element.getIndex(); if (index != 0 && index != 1) { builder.append('['); builder.append(index); builder.append(']'); } builder.append(" "); builder.append(element.get()); builder.append("\n"); } }, true); } return builder.toString(); } /** * Check consistency. */ private void checkConsistency() throws IllegalStateException { // assert: synchronized (cacheMonitor) if (!consistencyCheckEnabled) { return; } int elementsInCache = 0; Iterator iter = idCache.values().iterator(); while (iter.hasNext()) { LRUEntry entry = iter.next(); elementsInCache += entry.getElements().length; } class PathMapElementCounter implements PathMap.ElementVisitor { int count; public void elementVisited(PathMap.Element element) { LRUEntry mappedEntry = element.get(); LRUEntry cachedEntry = idCache.get(mappedEntry.getId()); if (cachedEntry == null) { String msg = "Path element (" + element + " ) cached in path map, associated id (" + mappedEntry.getId() + ") isn't."; throw new IllegalStateException(msg); } if (cachedEntry != mappedEntry) { String msg = "LRUEntry associated with element (" + element + " ) in path map is not equal to cached LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } PathMap.Element[] elements = cachedEntry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i] == element) { count++; return; } } String msg = "Element (" + element + ") cached in path map, but not in associated LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } } PathMapElementCounter counter = new PathMapElementCounter(); pathCache.traverse(counter, false); if (counter.count != elementsInCache) { String msg = "PathMap element and cached element count don't match (" + counter.count + " != " + elementsInCache + ")"; throw new IllegalStateException(msg); } } /** * 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 { /** * Previous entry */ private LRUEntry previous; /** * Next entry */ private LRUEntry next; /** * Node id */ private final NodeId id; /** * Elements in path map */ private PathMap.Element[] elements; /** * Create a new instance of this class * * @param id node id * @param element the path map element for this entry */ @SuppressWarnings("unchecked") public LRUEntry(NodeId id, PathMap.Element element) { this.id = id; this.elements = new PathMap.Element[] { element }; append(); } /** * Append entry to end of LRU list */ public void append() { if (tail == null) { head = this; tail = this; } else { previous = tail; tail.next = this; tail = this; } } /** * Remove entry from LRU list */ public void remove() { if (previous != null) { previous.next = next; } if (next != null) { next.previous = previous; } if (head == this) { head = next; } if (tail == this) { tail = previous; } previous = null; next = null; } /** * Touch entry. Removes it from its current position in the LRU list * and moves it to the end. */ public void touch() { remove(); append(); } /** * Return next LRU entry * * @return next LRU entry */ public LRUEntry getNext() { return next; } /** * Return node ID * * @return node ID */ public NodeId getId() { return id; } /** * Return elements in path map that are mapped to id. If * this entry is a shareable node or one of its descendant, it can * be reached by more than one path. * * @return element in path map */ public PathMap.Element[] getElements() { return elements; } /** * Add a mapping to some element. */ @SuppressWarnings("unchecked") public void addElement(PathMap.Element element) { PathMap.Element[] tmp = new PathMap.Element[elements.length + 1]; System.arraycopy(elements, 0, tmp, 0, elements.length); tmp[elements.length] = element; elements = tmp; } /** * Remove a mapping to some element from this entry. * * @return number of mappings left */ @SuppressWarnings("unchecked") public int removeElement(PathMap.Element element) { boolean found = false; for (int i = 0; i < elements.length; i++) { if (found) { elements[i - 1] = elements[i]; } else if (elements[i] == element) { found = true; } } if (found) { PathMap.Element[] tmp = new PathMap.Element[elements.length - 1]; System.arraycopy(elements, 0, tmp, 0, tmp.length); elements = tmp; } return elements.length; } /** * {@inheritDoc} */ public String toString() { return id.toString(); } } private final class CacheStatistics { private final String id; private final ReferenceMap cache; private long timeStamp = 0; public CacheStatistics() { this.id = cacheMonitor.toString(); this.cache = idCache; } public void log() { if (log.isDebugEnabled()) { long now = System.currentTimeMillis(); final String msg = "Cache id = {};size = {};max = {}"; if (log.isTraceEnabled()) { log.trace(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } else if (now > timeStamp + CACHE_STATISTICS_LOG_INTERVAL_MILLIS) { timeStamp = now; log.debug(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.security.Principal; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.security.AccessControlException; import javax.security.auth.Subject; import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.core.config.AccessManagerConfig; import org.apache.jackrabbit.core.config.LoginModuleConfig; import org.apache.jackrabbit.core.config.SecurityConfig; import org.apache.jackrabbit.core.config.SecurityManagerConfig; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; import org.apache.jackrabbit.core.config.UserManagerConfig; import org.apache.jackrabbit.core.security.AMContext; 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.GroupPrincipals; import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl; import org.apache.jackrabbit.core.security.principal.PrincipalProvider; import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl; import org.apache.jackrabbit.core.security.user.MembershipCache; import org.apache.jackrabbit.core.security.user.UserManagerImpl; import org.apache.jackrabbit.core.security.user.action.AuthorizableAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The security manager acts as central managing class for all security related * operations on a low-level non-protected level. It manages the * * {@link PrincipalProvider}s * {@link AccessControlProvider}s * {@link WorkspaceAccessManager} * {@link UserManager} * */ public class DefaultSecurityManager implements JackrabbitSecurityManager { /** * the default logger */ private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class); /** * Flag indicating if the security manager was properly initialized. */ private boolean initialized; /** * the repository implementation */ private RepositoryImpl repository; /** * System session. */ private SystemSession systemSession; /** * System user manager. Implementation needed here for the DefaultPrincipalProvider. */ private UserManager systemUserManager; /** * The user id of the administrator. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ADMIN_ID}). */ protected String adminId; /** * The user id of the anonymous user. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ANONYMOUS_ID}). */ protected String anonymousId; /** * Contains the access control providers per workspace. * key = name of the workspace, * value = {@link AccessControlProvider} */ private final Map acProviders = new HashMap(); /** * the AccessControlProviderFactory */ private AccessControlProviderFactory acProviderFactory; /** * the configured WorkspaceAccessManager */ private WorkspaceAccessManager workspaceAccessManager; /** * the principal provider registry */ private PrincipalProviderRegistry principalProviderRegistry; /** * factory for login-context {@see Repository#login()) */ private AuthContextProvider authContextProvider; //------------------------------------------< JackrabbitSecurityManager >--- /** * @see JackrabbitSecurityManager#init(Repository, Session) */ public synchronized void init(Repository repository, Session systemSession) throws RepositoryException { if (initialized) { throw new IllegalStateException("already initialized"); } if (!(repository instanceof RepositoryImpl)) { throw new RepositoryException("RepositoryImpl expected"); } if (!(systemSession instanceof SystemSession)) { throw new RepositoryException("SystemSession expected"); } this.systemSession = (SystemSession) systemSession; this.repository = (RepositoryImpl) repository; SecurityConfig config = this.repository.getConfig().getSecurityConfig(); LoginModuleConfig loginModConf = config.getLoginModuleConfig(); // build AuthContextProvider based on appName + optional LoginModuleConfig authContextProvider = new AuthContextProvider(config.getAppName(), loginModConf); if (authContextProvider.isLocal()) { log.info("init: use Repository Login-Configuration for " + config.getAppName()); } else if (authContextProvider.isJAAS()) { log.info("init: use JAAS login-configuration for " + config.getAppName()); } else { String msg = "Neither JAAS nor RepositoryConfig contained a valid configuration for " + config.getAppName(); log.error(msg); throw new RepositoryException(msg); } Properties[] moduleConfig = authContextProvider.getModuleConfig(); // retrieve default-ids (admin and anonymous) from login-module-configuration. for (Properties props : moduleConfig) { if (props.containsKey(LoginModuleConfig.PARAM_ADMIN_ID)) { adminId = props.getProperty(LoginModuleConfig.PARAM_ADMIN_ID); } if (props.containsKey(LoginModuleConfig.PARAM_ANONYMOUS_ID)) { anonymousId = props.getProperty(LoginModuleConfig.PARAM_ANONYMOUS_ID); } } // fallback: if (adminId == null) { log.debug("No adminID defined in LoginModule/JAAS config -> using default."); adminId = SecurityConstants.ADMIN_ID; } if (anonymousId == null) { log.debug("No anonymousID defined in LoginModule/JAAS config -> using default."); anonymousId = SecurityConstants.ANONYMOUS_ID; } // create the system userManager and make sure the system-users exist. systemUserManager = createUserManager(this.systemSession); createSystemUsers(systemUserManager, this.systemSession, adminId, anonymousId); // init default ac-provider-factory acProviderFactory = new AccessControlProviderFactoryImpl(); acProviderFactory.init(this.systemSession); // create the workspace access manager SecurityManagerConfig smc = config.getSecurityManagerConfig(); if (smc != null && smc.getWorkspaceAccessConfig() != null) { workspaceAccessManager = smc.getWorkspaceAccessConfig().newInstance(WorkspaceAccessManager.class); } else { // fallback -> the default implementation log.debug("No WorkspaceAccessManager configured; using default."); workspaceAccessManager = createDefaultWorkspaceAccessManager(); } workspaceAccessManager.init(this.systemSession); // initialize principal-provider registry // 1) create default PrincipalProvider defaultPP = createDefaultPrincipalProvider(moduleConfig); // 2) create registry instance principalProviderRegistry = new ProviderRegistryImpl(defaultPP); // 3) register all configured principal providers. for (Properties props : moduleConfig) { principalProviderRegistry.registerProvider(props); } initialized = true; } /** * @see JackrabbitSecurityManager#dispose(String) */ public void dispose(String workspaceName) { checkInitialized(); synchronized (acProviders) { AccessControlProvider prov = acProviders.remove(workspaceName); if (prov != null) { prov.close(); } } } /** * @see JackrabbitSecurityManager#close() */ public void close() { checkInitialized(); synchronized (acProviders) { for (AccessControlProvider accessControlProvider : acProviders.values()) { accessControlProvider.close(); } acProviders.clear(); } } /** * @see JackrabbitSecurityManager#getAccessManager(Session,AMContext) */ public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { checkInitialized(); AccessManagerConfig amConfig = repository.getConfig().getSecurityConfig().getAccessManagerConfig(); try { String wspName = session.getWorkspace().getName(); AccessControlProvider pp = getAccessControlProvider(wspName); AccessManager accessMgr; if (amConfig == null) { log.debug("No configuration entry for AccessManager. Using org.apache.jackrabbit.core.security.DefaultAccessManager"); accessMgr = new DefaultAccessManager(); } else { accessMgr = amConfig.newInstance(AccessManager.class); } accessMgr.init(amContext, pp, workspaceAccessManager); return accessMgr; } catch (AccessDeniedException e) { // re-throw throw e; } catch (Exception e) { // wrap in RepositoryException String clsName = (amConfig == null) ? "-- missing access manager configuration --" : amConfig.getClassName(); String msg = "Failed to instantiate AccessManager (" + clsName + ")"; log.error(msg, e); throw new RepositoryException(msg, e); } } /** * @see JackrabbitSecurityManager#getPrincipalManager(Session) */ public PrincipalManager getPrincipalManager(Session session) throws RepositoryException { checkInitialized(); if (session instanceof SessionImpl) { SessionImpl sImpl = (SessionImpl) session; return createPrincipalManager(sImpl); } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserManager(Session) */ public UserManager getUserManager(Session session) throws RepositoryException { checkInitialized(); if (session == systemSession) { return systemUserManager; } else if (session instanceof SessionImpl) { String workspaceName = systemSession.getWorkspace().getName(); try { SessionImpl sImpl = (SessionImpl) session; UserManagerImpl uMgr; if (workspaceName.equals(sImpl.getWorkspace().getName())) { uMgr = createUserManager(sImpl); } else { SessionImpl s = (SessionImpl) sImpl.createSession(workspaceName); uMgr = createUserManager(s); sImpl.addListener(uMgr); } return uMgr; } catch (NoSuchWorkspaceException e) { throw new AccessControlException("Cannot build UserManager for " + session.getUserID(), e); } } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserID(javax.security.auth.Subject, String) */ public String getUserID(Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); // 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(); if (cl != null) { Set s = subject.getPrincipals(cl); if (!s.isEmpty()) { for (Principal p : s) { if (!GroupPrincipals.isGroup(p)) { return p.getName(); } } // all principals found with the given p-Class were Group principals log.debug("Only Group principals found with class '" + cl.getName() + "' -> Not used for UserID."); } else { log.debug("No principal found with class '" + cl.getName() + "'."); } } /* Fallback scenario to retrieve userID from the subject: Since the subject may contain multiple principals and the principal name may not be equals to the UserID, the id is retrieved by searching for the corresponding authorizable and if this doesn't succeed an attempt is made to obtained it from the login-credentials. */ String uid = null; // first try to retrieve an authorizable corresponding to // a non-group principal. the first one present is used // to determine the userID. try { UserManager umgr = getSystemUserManager(workspaceName); for (Principal p : subject.getPrincipals()) { if (!(p instanceof Group)) { Authorizable authorz = umgr.getAuthorizable(p); if (authorz != null && !authorz.isGroup()) { uid = authorz.getID(); break; } } } } catch (RepositoryException e) { // failed to access userid via user manager -> use fallback 2. log.error("Unexpected error while retrieving UserID.", e); } // 2. if no matching user is found try simple access to userID over // SimpleCredentials. if (uid == null) { Iterator creds = subject.getPublicCredentials( SimpleCredentials.class).iterator(); if (creds.hasNext()) { SimpleCredentials sc = creds.next(); uid = sc.getUserID(); } } return uid; } /** * Creates an AuthContext for the given {@link Credentials} and * {@link Subject}. The workspace name is ignored and users are * stored and retrieved from a specific (separate) workspace. * This includes selection of application specific LoginModules and * initialization with credentials and Session to System-Workspace * * @return an {@link AuthContext} for the given Credentials, Subject * @throws RepositoryException in other exceptional repository states */ public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); return getAuthContextProvider().getAuthContext(creds, subject, systemSession, getPrincipalProviderRegistry(), adminId, anonymousId); } //----------------------------------------------------------< protected >--- /** * @return The SecurityManagerConfig configured for the * repository this manager has been created for. */ protected SecurityManagerConfig getConfig() { return repository.getConfig().getSecurityConfig().getSecurityManagerConfig(); } /** * @param workspaceName The name of the target workspace. * @return The system user manager. Since this implementation stores users * in a dedicated workspace the system user manager is the same for all * sessions irrespective of the workspace. * @throws javax.jcr.RepositoryException If an error occurs. */ protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException { return systemUserManager; } /** * @param session The session for which to retrieve the membership cache. * @return The membership cache. * @throws RepositoryException If an error occurs. */ protected MembershipCache getMembershipCache(SessionImpl session) throws RepositoryException { if (session == systemSession || session instanceof SystemSession) { // force creation of the membership cache within the corresponding uMgr return null; } else { return ((UserManagerImpl) getSystemUserManager(session.getWorkspace().getName())).getMembershipCache(); } } /** * Creates a {@link UserManagerImpl} for the given session. May be overridden * to return a custom implementation. * * @param session session * @return user manager * @throws RepositoryException if an error occurs */ protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException { UserManagerConfig umc = getConfig().getUserManagerConfig(); UserManagerImpl um; if (umc != null) { Class>[] paramTypes = new Class[] { SessionImpl.class, String.class, Properties.class, MembershipCache.class}; um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class, paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session)); } else { um = new UserManagerImpl(session, adminId, null, getMembershipCache(session)); } if (umc != null && !(session instanceof SystemSession)) { AuthorizableAction[] actions = umc.getAuthorizableActions(); um.setAuthorizableActions(actions); } return um; } /** * @param session The session used to create the principal manager. * @return A new instance of PrincipalManagerImpl * @throws javax.jcr.RepositoryException If an error occurs. */ protected PrincipalManager createPrincipalManager(SessionImpl session) throws RepositoryException { return new PrincipalManagerImpl(session, getPrincipalProviderRegistry().getProviders()); } /** * @return A nwe instance of WorkspaceAccessManagerImpl to be used as * default workspace access manager if the configuration doesn't specify one. */ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { return new WorkspaceAccessManagerImpl(); } /** * Creates the default principal provider used to create the * {@link PrincipalProviderRegistry}. * * @return An new instance of DefaultPrincipalProvider. * @throws RepositoryException If an error occurs. */ protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { boolean initialized = false; PrincipalProvider defaultPP = new DefaultPrincipalProvider(this.systemSession, (UserManagerImpl) systemUserManager); 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; } /** * @return The PrincipalProviderRegistry created during initialization. */ protected PrincipalProviderRegistry getPrincipalProviderRegistry() { return principalProviderRegistry; } /** * @return The AuthContextProvider created during initialization. */ protected AuthContextProvider getAuthContextProvider() { return authContextProvider; } /** * Throws IllegalStateException if this manager hasn't been * initialized. */ protected void checkInitialized() { if (!initialized) { throw new IllegalStateException("Not initialized"); } } /** * @return The system session used to initialize this SecurityManager. */ protected Session getSystemSession() { return systemSession; } /** * @return The repository used to initialize this SecurityManager. */ protected Repository getRepository() { return repository; } //-------------------------------------------------------------------------- /** * Returns the access control provider for the specified * workspaceName. * * @param workspaceName Name of the workspace. * @return access control provider * @throws NoSuchWorkspaceException If no workspace with 'workspaceName' exists. * @throws RepositoryException */ private AccessControlProvider getAccessControlProvider(String workspaceName) throws NoSuchWorkspaceException, RepositoryException { checkInitialized(); AccessControlProvider provider = acProviders.get(workspaceName); if (provider == null || !provider.isLive()) { // mark this workspace as 'active' so the workspace does not // get disposed by the workspace-janitor // TODO: There should be a cleaner way to do this. repository.markWorkspaceActive(workspaceName); WorkspaceSecurityConfig secConf = null; WorkspaceConfig conf = repository.getConfig().getWorkspaceConfig(workspaceName); if (conf != null) { secConf = conf.getSecurityConfig(); } provider = acProviderFactory.createProvider( repository.getSystemSession(workspaceName), secConf); synchronized (acProviders) { acProviders.put(workspaceName, provider); } } return provider; } /** * Make sure the system users (admin and anonymous) exist. * * @param userManager Manager to create users/groups. * @param session The editing session. * @param adminId UserID of the administrator. * @param anonymousId UserID of the anonymous user. * @throws RepositoryException If an error occurs. */ static void createSystemUsers(UserManager userManager, SystemSession session, String adminId, String anonymousId) throws RepositoryException { Authorizable admin; if (adminId != null) { admin = userManager.getAuthorizable(adminId); if (admin == null) { userManager.createUser(adminId, adminId); if (!userManager.isAutoSave()) { session.save(); } log.info("... created admin-user with id \'" + adminId + "\' ..."); } } if (anonymousId != null) { Authorizable anonymous = userManager.getAuthorizable(anonymousId); if (anonymous == null) { try { userManager.createUser(anonymousId, ""); if (!userManager.isAutoSave()) { session.save(); } log.info("... created anonymous user with id \'" + anonymousId + "\' ..."); } catch (RepositoryException e) { // exception while creating the anonymous user. // log an error but don't abort the repository start-up log.error("Failed to create anonymous user.", e); } } } } //------------------------------------------------------< inner classes >--- /** * WorkspaceAccessManager that upon {@link #grants(Set principals, String)} * evaluates if access to the root node of a workspace with the specified * name is granted. */ private final class WorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { //-----------------------------------------< WorkspaceAccessManager >--- /** * {@inheritDoc} */ public void init(Session systemSession) throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public void close() throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public boolean grants(Set principals, String workspaceName) throws RepositoryException { AccessControlProvider prov = getAccessControlProvider(workspaceName); return prov.canAccessRoot(principals); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ItemNotFoundException; import javax.jcr.RepositoryException; 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.spi.Name; import org.apache.jackrabbit.spi.Path; /** * The HierarchyManager interface ... */ public interface HierarchyManager { /** * Resolves a path into an item id. * * If there is both a node and a property at the specified path, this method * will return the id of the node. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * item to be found at path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #resolveNodePath(Path)} and * {@link #resolvePropertyPath(Path)} should be used instead. * * @param path path to resolve * @return item id referred to by path or null * if there's no item at path. * @throws RepositoryException if an error occurs */ @Deprecated ItemId resolvePath(Path path) throws RepositoryException; /** * Resolves a path into a node id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * node to be found at path. * * @param path path to resolve * @return node id referred to by path or null * if there's no node at path. * @throws RepositoryException if an error occurs */ NodeId resolveNodePath(Path path) throws RepositoryException; /** * Resolves a path into a property id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * property to be found at path. * * @param path path to resolve * @return property id referred to by path or null * if there's no property at path. * @throws RepositoryException if an error occurs */ PropertyId resolvePropertyPath(Path path) throws RepositoryException; /** * Returns the path to the given item. * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item. * @param id id of item whose name should be returned * @return * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item, with the given parent id. If the * given item is not shareable, this is identical to {@link #getName(ItemId)}. * * @param id node id * @param parentId parent node id * @return name * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified item which is equivalent to * getPath(id).getAncestorCount(). The depth reflects the * absolute hierarchy level. * * @param id item id * @return the depth of the specified item * @throws ItemNotFoundException if the specified id does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified descendant relative to the given * ancestor. If ancestorId and descendantId * denote the same item 0 is returned. If ancestorId does not * denote an ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestorId does not * denote an ancestor of the item denoted by descendantId * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; /** * Determines whether the node with the specified nodeId * is an ancestor of the item denoted by the given itemId. * This is equivalent to * getPath(nodeId).isAncestorOf(getPath(itemId)). * * @param nodeId node id * @param itemId item id * @return true if the node with the specified * nodeId is an ancestor of the item denoted by the * given itemId; false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException; //------------------------------------------- operation with shareable nodes /** * Determines whether the node with the specified ancestor * is a share ancestor of the item denoted by the given descendant. * This is true for two nodes A, B * if either: * * A is a (proper) ancestor of B * there is a non-empty sequence of nodes N1,... * ,Nk such that A= * N1 and B=Nk * and Ni is the parent or a share-parent of * Ni+1 (for every i in 1 * ...k-1. * * * @param ancestor node id * @param descendant item id * @return true if the node denoted by ancestor * is a share ancestor of the item denoted by descendant, * false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified share-descendant relative to the given * share-ancestor. If ancestor and descendant * denote the same item, 0 is returned. If ancestor * does not denote an share-ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestor does * not denote a share-ancestor of the item denoted by descendant * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getShareRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; 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.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; 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.PropertyState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HierarchyManagerImpl ... */ public class HierarchyManagerImpl implements HierarchyManager { private static Logger log = LoggerFactory.getLogger(HierarchyManagerImpl.class); /** * The parent name returned for orphaned or root nodes. * TODO: Is it proper to use an invalid Name for this. */ private static final Name EMPTY_NAME = NameFactoryImpl.getInstance().create("", ""); protected final NodeId rootNodeId; protected final ItemStateManager provider; /** * Flags describing what items to return in {@link #resolvePath(Path, int)}. */ static final int RETURN_NODE = 1; static final int RETURN_PROPERTY = 2; static final int RETURN_ANY = (RETURN_NODE | RETURN_PROPERTY); public HierarchyManagerImpl(NodeId rootNodeId, ItemStateManager provider) { this.rootNodeId = rootNodeId; this.provider = provider; } public NodeId getRootNodeId() { return rootNodeId; } //-------------------------------------------------------< implementation > /** * Internal implementation that iteratively resolves a path into an item. * * @param elements path elements * @param next index of next item in elements to inspect * @param id id of item at path elements[0]..elements[next - 1] * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws ItemStateException if an intermediate item state is not found * @throws MalformedPathException if building an intermediate path fails */ protected ItemId resolvePath(Path.Element[] elements, int next, ItemId id, int typesAllowed) throws ItemStateException, MalformedPathException { PathBuilder builder = new PathBuilder(); for (int i = 0; i < next; i++) { builder.addLast(elements[i]); } for (int i = next; i < elements.length; i++) { Path.Element elem = elements[i]; NodeId parentId = (NodeId) id; id = null; Name name = elem.getName(); int index = elem.getIndex(); if (index == 0) { index = 1; } int typeExpected = typesAllowed; if (i < elements.length - 1) { // intermediate items must always be nodes typeExpected = RETURN_NODE; } NodeState parentState = (NodeState) getItemState(parentId); if ((typeExpected & RETURN_NODE) != 0) { ChildNodeEntry nodeEntry = getChildNodeEntry(parentState, name, index); if (nodeEntry != null) { id = nodeEntry.getId(); } } if (id == null && (typeExpected & RETURN_PROPERTY) != 0) { if (parentState.hasPropertyName(name) && (index <= 1)) { // property id = new PropertyId(parentState.getNodeId(), name); } } if (id == null) { break; } builder.addLast(elements[i]); pathResolved(id, builder); } return id; } //---------------------------------------------------------< overridables > /** * Return an item state, given its item id. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return item state * @throws NoSuchItemStateException if the item does not exist * @throws ItemStateException if an error occurs * @see ZombieHierarchyManager#getItemState(ItemId) */ protected ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { return provider.getItemState(id); } /** * Determines whether an item state for a given item id exists. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return true if an item state exists, otherwise * false * @see ZombieHierarchyManager#hasItemState(ItemId) */ protected boolean hasItemState(ItemId id) { return provider.hasItemState(id); } /** * Returns the parentUUID of the given item. * * Low-level hook provided for specialized derived classes. * * @param state item state * @return parentUUID of the given item * @see ZombieHierarchyManager#getParentId(ItemState) */ protected NodeId getParentId(ItemState state) { return state.getParentId(); } /** * Return all parents of a node. A shareable node has possibly more than * one parent. * * @param state item state * @param useOverlayed whether to use overlayed state for shareable nodes * @return set of parent NodeIds. If state has no parent, * array has length 0. */ protected Set getParentIds(ItemState state, boolean useOverlayed) { if (state.isNode()) { // if this is a node, quickly check whether it is shareable and // whether it contains more than one parent NodeState ns = (NodeState) state; if (ns.isShareable() && useOverlayed && ns.hasOverlayedState()) { ns = (NodeState) ns.getOverlayedState(); } Set s = ns.getSharedSet(); if (s.size() > 1) { return s; } } NodeId parentId = getParentId(state); if (parentId != null) { LinkedHashSet s = new LinkedHashSet(); s.add(parentId); return s; } return Collections.emptySet(); } /** * Returns the ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
state
* Overridden method simply checks whether we have an item matching the id * and returns its path, otherwise calls base implementation. */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { try { return element.getPath(); } catch (MalformedPathException mpe) { String msg = "Failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } } return super.getPath(id); } /** * {@inheritDoc} */ public Name getName(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getName(); } } return super.getName(id); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { if (id.denotesNode()) { PathMap.Element element = get(id); if (element != null) { return element.getDepth(); } } return super.getDepth(id); } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { PathMap.Element element = get(nodeId); if (element != null) { PathMap.Element child = get(itemId); if (child != null) { return element.isAncestorOf(child); } } } return super.isAncestor(nodeId, itemId); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { if (modified.isNode()) { nodeModified((NodeState) modified); } } /** * {@inheritDoc} * * If path information is cached for modified, this iterates * over all child nodes in the path map, evicting the ones that do not * (longer) exist in the underlying NodeState. */ public void nodeModified(NodeState modified) { synchronized (cacheMonitor) { for (PathMap.Element element : getCachedPaths(modified.getNodeId())) { for (PathMap.Element child : element.getChildren()) { ChildNodeEntry cne = modified.getChildNodeEntry( child.getName(), child.getNormalizedIndex()); if (cne == null) { // Item does not exist, remove evict(child, true); } else { LRUEntry childEntry = child.get(); if (childEntry != null && !cne.getId().equals(childEntry.getId())) { // Different child item, remove evict(child, true); } } } } checkConsistency(); } } private List> getCachedPaths(NodeId id) { // JCR-2720: Handle the root path as a special case if (rootNodeId.equals(id)) { return Collections.singletonList(pathCache.map( PathFactoryImpl.getInstance().getRootPath(), true)); } LRUEntry entry = idCache.get(id); if (entry != null) { return Arrays.asList(entry.getElements()); } else { return Collections.emptyList(); } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { evictAll(destroyed.getId(), true); } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { if (discarded.isTransient() && !discarded.hasOverlayedState() && discarded.getStatus() == ItemState.STATUS_NEW) { // a new node has been discarded -> remove from cache evictAll(discarded.getId(), true); } else if (provider.hasItemState(discarded.getId())) { evictAll(discarded.getId(), false); } else { evictAll(discarded.getId(), true); } } /** * {@inheritDoc} */ public void nodeAdded(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to find item " + state.getNodeId(), e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } 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} * * Iterate over all cached children of this state and verify each * child's position. */ public void nodesReplaced(NodeState state) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(state.getNodeId()); if (entry == null) { return; } for (PathMap.Element parent : entry.getElements()) { HashMap> newChildrenOrder = new HashMap>(); boolean orderChanged = false; for (PathMap.Element child : parent.getChildren()) { LRUEntry childEntry = child.get(); if (childEntry == null) { // Child has no associated UUID information: we're // therefore unable to determine if this child's // position is still accurate and have to assume // the worst and remove it. evict(child, false); } else { NodeId childId = childEntry.getId(); ChildNodeEntry cne = state.getChildNodeEntry(childId); if (cne == null) { // Child no longer in parent node, so remove it evict(child, false); } else { // Put all children into map of new children order // - regardless whether their position changed or // not - as we might need to reorder them later on. Path.Element newNameIndex = PathFactoryImpl.getInstance().createElement( cne.getName(), cne.getIndex()); newChildrenOrder.put(newNameIndex, child); if (!newNameIndex.equals(child.getPathElement())) { orderChanged = true; } } } } if (orderChanged) { /* If at least one child changed its position, reorder */ parent.setChildren(newChildrenOrder); } } checkConsistency(); } } /** * {@inheritDoc} */ public void nodeRemoved(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to get path of " + state.getNodeId(), e); } 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); } } } //------------------------------------------------------< private methods > /** * Return the first cached path that is mapped to given id. * * @param id node id * @return cached element, null if not found */ private PathMap.Element get(ItemId id) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { entry.touch(); return entry.getElements()[0]; } return null; } } /** * Return the nearest cached element in the path map, given a path. * The returned element is guaranteed to have an associated object that * is not null. * * @param path path * @return cached element, null if not found */ private PathMap.Element map(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, false); while (element != null) { LRUEntry entry = element.get(); if (entry != null) { entry.touch(); return element; } element = element.getParent(); } return null; } } /** * Cache an item in the hierarchy given its id and path. * * @param id node id * @param path path to item */ private void cache(NodeId id, Path path) { synchronized (cacheMonitor) { if (isCached(id, path)) { return; } if (idCache.size() >= upperLimit) { idCacheStatistics.log(); /** * Remove least recently used item. Scans the LRU list from * head to tail and removes the first item that has no children. */ LRUEntry entry = head; while (entry != null) { PathMap.Element[] elements = entry.getElements(); int childrenCount = 0; for (int i = 0; i < elements.length; i++) { childrenCount += elements[i].getChildrenCount(); } if (childrenCount == 0) { evictAll(entry.getId(), false); return; } entry = entry.getNext(); } } PathMap.Element element = pathCache.put(path); if (element.get() != null) { if (!id.equals((element.get()).getId())) { log.debug("overwriting PathMap.Element"); } } LRUEntry entry = idCache.get(id); if (entry == null) { entry = new LRUEntry(id, element); idCache.put(id, entry); } else { entry.addElement(element); } element.set(entry); checkConsistency(); } } /** * Return a flag indicating whether a certain node and/or path is cached. * If path is null, check whether the item is * cached at all. If path is not null, * check whether the item is cached with that path. * * @param id item id * @param path path, may be null * @return true if the item is already cached; * false otherwise */ boolean isCached(NodeId id, Path path) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry == null) { return false; } if (path == null) { return true; } PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i].hasPath(path)) { return true; } } return false; } } /** * Return a flag indicating whether a certain path is cached. * * @param path item path * @return true if the item is already cached; * false otherwise */ boolean isCached(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, true); if (element != null) { return element.get() != null; } return false; } } /** * Remove all path mapping for a given item id. Removes the associated * LRUEntry and the PathMap.Element with it. * Indexes of same name sibling elements are shifted! * * @param id item id */ private void evictAll(ItemId id, boolean shift) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { evict(elements[i], shift); } } checkConsistency(); } } /** * Evict path map element from cache. This will traverse all children * of this element and remove the objects associated with them. * Index of same name sibling items are shifted! * * @param element path map element */ private void evict(PathMap.Element element, boolean shift) { // assert: synchronized (cacheMonitor) element.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { LRUEntry entry = element.get(); if (entry.removeElement(element) == 0) { idCache.remove(entry.getId()); entry.remove(); } } }, false); element.remove(shift); } /** * Invoked when a notification about a child node addition has been received. * * @param state node state where child was added * @param path path to child node * @param id child node id * * @throws PathNotFoundException if the path was not found * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeAdded(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element element = null; LRUEntry entry = idCache.get(id); if (entry != null) { // child node already cached: this can have the following // reasons: // 1) node was moved, cached path is outdated // 2) node was cloned, cached path is still valid NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { PathMap.Element[] elements = entry.getElements(); element = elements[0]; for (int i = 0; i < elements.length; i++) { elements[i].remove(); } } } PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent != null) { parent.insert(path.getNameElement()); } if (element != null) { // store remembered element at new position pathCache.put(path, element); } } /** * Invoked when a notification about a child node removal has been received. * * @param state node state * @param path node path * @param id node id * * @throws PathNotFoundException if the path was not found. * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeRemoved(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent == null) { return; } PathMap.Element element = parent.getDescendant(path.getLastElement(), true); if (element != null) { // with SNS, this might evict a child that is NOT the one // having id, check first whether item has // the id passed as argument LRUEntry entry = element.get(); if (entry != null && !entry.getId().equals(id)) { return; } // if item is shareable, remove this path only, otherwise // every path this item has been mapped to NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { evictAll(id, true); } else { evict(element, true); } } else { // element itself is not cached, but removal might cause SNS // index shifting parent.remove(path.getNameElement()); } } /** * Dump contents of path map and elements included to a string. */ public String toString() { final StringBuilder builder = new StringBuilder(); synchronized (cacheMonitor) { pathCache.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { for (int i = 0; i < element.getDepth(); i++) { builder.append("--"); } builder.append(element.getName()); int index = element.getIndex(); if (index != 0 && index != 1) { builder.append('['); builder.append(index); builder.append(']'); } builder.append(" "); builder.append(element.get()); builder.append("\n"); } }, true); } return builder.toString(); } /** * Check consistency. */ private void checkConsistency() throws IllegalStateException { // assert: synchronized (cacheMonitor) if (!consistencyCheckEnabled) { return; } int elementsInCache = 0; Iterator iter = idCache.values().iterator(); while (iter.hasNext()) { LRUEntry entry = iter.next(); elementsInCache += entry.getElements().length; } class PathMapElementCounter implements PathMap.ElementVisitor { int count; public void elementVisited(PathMap.Element element) { LRUEntry mappedEntry = element.get(); LRUEntry cachedEntry = idCache.get(mappedEntry.getId()); if (cachedEntry == null) { String msg = "Path element (" + element + " ) cached in path map, associated id (" + mappedEntry.getId() + ") isn't."; throw new IllegalStateException(msg); } if (cachedEntry != mappedEntry) { String msg = "LRUEntry associated with element (" + element + " ) in path map is not equal to cached LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } PathMap.Element[] elements = cachedEntry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i] == element) { count++; return; } } String msg = "Element (" + element + ") cached in path map, but not in associated LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } } PathMapElementCounter counter = new PathMapElementCounter(); pathCache.traverse(counter, false); if (counter.count != elementsInCache) { String msg = "PathMap element and cached element count don't match (" + counter.count + " != " + elementsInCache + ")"; throw new IllegalStateException(msg); } } /** * 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 { /** * Previous entry */ private LRUEntry previous; /** * Next entry */ private LRUEntry next; /** * Node id */ private final NodeId id; /** * Elements in path map */ private PathMap.Element[] elements; /** * Create a new instance of this class * * @param id node id * @param element the path map element for this entry */ @SuppressWarnings("unchecked") public LRUEntry(NodeId id, PathMap.Element element) { this.id = id; this.elements = new PathMap.Element[] { element }; append(); } /** * Append entry to end of LRU list */ public void append() { if (tail == null) { head = this; tail = this; } else { previous = tail; tail.next = this; tail = this; } } /** * Remove entry from LRU list */ public void remove() { if (previous != null) { previous.next = next; } if (next != null) { next.previous = previous; } if (head == this) { head = next; } if (tail == this) { tail = previous; } previous = null; next = null; } /** * Touch entry. Removes it from its current position in the LRU list * and moves it to the end. */ public void touch() { remove(); append(); } /** * Return next LRU entry * * @return next LRU entry */ public LRUEntry getNext() { return next; } /** * Return node ID * * @return node ID */ public NodeId getId() { return id; } /** * Return elements in path map that are mapped to id. If * this entry is a shareable node or one of its descendant, it can * be reached by more than one path. * * @return element in path map */ public PathMap.Element[] getElements() { return elements; } /** * Add a mapping to some element. */ @SuppressWarnings("unchecked") public void addElement(PathMap.Element element) { PathMap.Element[] tmp = new PathMap.Element[elements.length + 1]; System.arraycopy(elements, 0, tmp, 0, elements.length); tmp[elements.length] = element; elements = tmp; } /** * Remove a mapping to some element from this entry. * * @return number of mappings left */ @SuppressWarnings("unchecked") public int removeElement(PathMap.Element element) { boolean found = false; for (int i = 0; i < elements.length; i++) { if (found) { elements[i - 1] = elements[i]; } else if (elements[i] == element) { found = true; } } if (found) { PathMap.Element[] tmp = new PathMap.Element[elements.length - 1]; System.arraycopy(elements, 0, tmp, 0, tmp.length); elements = tmp; } return elements.length; } /** * {@inheritDoc} */ public String toString() { return id.toString(); } } private final class CacheStatistics { private final String id; private final ReferenceMap cache; private long timeStamp = 0; public CacheStatistics() { this.id = cacheMonitor.toString(); this.cache = idCache; } public void log() { if (log.isDebugEnabled()) { long now = System.currentTimeMillis(); final String msg = "Cache id = {};size = {};max = {}"; if (log.isTraceEnabled()) { log.trace(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } else if (now > timeStamp + CACHE_STATISTICS_LOG_INTERVAL_MILLIS) { timeStamp = now; log.debug(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.security.Principal; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.security.AccessControlException; import javax.security.auth.Subject; import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.core.config.AccessManagerConfig; import org.apache.jackrabbit.core.config.LoginModuleConfig; import org.apache.jackrabbit.core.config.SecurityConfig; import org.apache.jackrabbit.core.config.SecurityManagerConfig; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; import org.apache.jackrabbit.core.config.UserManagerConfig; import org.apache.jackrabbit.core.security.AMContext; 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.GroupPrincipals; import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl; import org.apache.jackrabbit.core.security.principal.PrincipalProvider; import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl; import org.apache.jackrabbit.core.security.user.MembershipCache; import org.apache.jackrabbit.core.security.user.UserManagerImpl; import org.apache.jackrabbit.core.security.user.action.AuthorizableAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The security manager acts as central managing class for all security related * operations on a low-level non-protected level. It manages the * * {@link PrincipalProvider}s * {@link AccessControlProvider}s * {@link WorkspaceAccessManager} * {@link UserManager} * */ public class DefaultSecurityManager implements JackrabbitSecurityManager { /** * the default logger */ private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class); /** * Flag indicating if the security manager was properly initialized. */ private boolean initialized; /** * the repository implementation */ private RepositoryImpl repository; /** * System session. */ private SystemSession systemSession; /** * System user manager. Implementation needed here for the DefaultPrincipalProvider. */ private UserManager systemUserManager; /** * The user id of the administrator. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ADMIN_ID}). */ protected String adminId; /** * The user id of the anonymous user. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ANONYMOUS_ID}). */ protected String anonymousId; /** * Contains the access control providers per workspace. * key = name of the workspace, * value = {@link AccessControlProvider} */ private final Map acProviders = new HashMap(); /** * the AccessControlProviderFactory */ private AccessControlProviderFactory acProviderFactory; /** * the configured WorkspaceAccessManager */ private WorkspaceAccessManager workspaceAccessManager; /** * the principal provider registry */ private PrincipalProviderRegistry principalProviderRegistry; /** * factory for login-context {@see Repository#login()) */ private AuthContextProvider authContextProvider; //------------------------------------------< JackrabbitSecurityManager >--- /** * @see JackrabbitSecurityManager#init(Repository, Session) */ public synchronized void init(Repository repository, Session systemSession) throws RepositoryException { if (initialized) { throw new IllegalStateException("already initialized"); } if (!(repository instanceof RepositoryImpl)) { throw new RepositoryException("RepositoryImpl expected"); } if (!(systemSession instanceof SystemSession)) { throw new RepositoryException("SystemSession expected"); } this.systemSession = (SystemSession) systemSession; this.repository = (RepositoryImpl) repository; SecurityConfig config = this.repository.getConfig().getSecurityConfig(); LoginModuleConfig loginModConf = config.getLoginModuleConfig(); // build AuthContextProvider based on appName + optional LoginModuleConfig authContextProvider = new AuthContextProvider(config.getAppName(), loginModConf); if (authContextProvider.isLocal()) { log.info("init: use Repository Login-Configuration for " + config.getAppName()); } else if (authContextProvider.isJAAS()) { log.info("init: use JAAS login-configuration for " + config.getAppName()); } else { String msg = "Neither JAAS nor RepositoryConfig contained a valid configuration for " + config.getAppName(); log.error(msg); throw new RepositoryException(msg); } Properties[] moduleConfig = authContextProvider.getModuleConfig(); // retrieve default-ids (admin and anonymous) from login-module-configuration. for (Properties props : moduleConfig) { if (props.containsKey(LoginModuleConfig.PARAM_ADMIN_ID)) { adminId = props.getProperty(LoginModuleConfig.PARAM_ADMIN_ID); } if (props.containsKey(LoginModuleConfig.PARAM_ANONYMOUS_ID)) { anonymousId = props.getProperty(LoginModuleConfig.PARAM_ANONYMOUS_ID); } } // fallback: if (adminId == null) { log.debug("No adminID defined in LoginModule/JAAS config -> using default."); adminId = SecurityConstants.ADMIN_ID; } if (anonymousId == null) { log.debug("No anonymousID defined in LoginModule/JAAS config -> using default."); anonymousId = SecurityConstants.ANONYMOUS_ID; } // create the system userManager and make sure the system-users exist. systemUserManager = createUserManager(this.systemSession); createSystemUsers(systemUserManager, this.systemSession, adminId, anonymousId); // init default ac-provider-factory acProviderFactory = new AccessControlProviderFactoryImpl(); acProviderFactory.init(this.systemSession); // create the workspace access manager SecurityManagerConfig smc = config.getSecurityManagerConfig(); if (smc != null && smc.getWorkspaceAccessConfig() != null) { workspaceAccessManager = smc.getWorkspaceAccessConfig().newInstance(WorkspaceAccessManager.class); } else { // fallback -> the default implementation log.debug("No WorkspaceAccessManager configured; using default."); workspaceAccessManager = createDefaultWorkspaceAccessManager(); } workspaceAccessManager.init(this.systemSession); // initialize principal-provider registry // 1) create default PrincipalProvider defaultPP = createDefaultPrincipalProvider(moduleConfig); // 2) create registry instance principalProviderRegistry = new ProviderRegistryImpl(defaultPP); // 3) register all configured principal providers. for (Properties props : moduleConfig) { principalProviderRegistry.registerProvider(props); } initialized = true; } /** * @see JackrabbitSecurityManager#dispose(String) */ public void dispose(String workspaceName) { checkInitialized(); synchronized (acProviders) { AccessControlProvider prov = acProviders.remove(workspaceName); if (prov != null) { prov.close(); } } } /** * @see JackrabbitSecurityManager#close() */ public void close() { checkInitialized(); synchronized (acProviders) { for (AccessControlProvider accessControlProvider : acProviders.values()) { accessControlProvider.close(); } acProviders.clear(); } } /** * @see JackrabbitSecurityManager#getAccessManager(Session,AMContext) */ public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { checkInitialized(); AccessManagerConfig amConfig = repository.getConfig().getSecurityConfig().getAccessManagerConfig(); try { String wspName = session.getWorkspace().getName(); AccessControlProvider pp = getAccessControlProvider(wspName); AccessManager accessMgr; if (amConfig == null) { log.debug("No configuration entry for AccessManager. Using org.apache.jackrabbit.core.security.DefaultAccessManager"); accessMgr = new DefaultAccessManager(); } else { accessMgr = amConfig.newInstance(AccessManager.class); } accessMgr.init(amContext, pp, workspaceAccessManager); return accessMgr; } catch (AccessDeniedException e) { // re-throw throw e; } catch (Exception e) { // wrap in RepositoryException String clsName = (amConfig == null) ? "-- missing access manager configuration --" : amConfig.getClassName(); String msg = "Failed to instantiate AccessManager (" + clsName + ")"; log.error(msg, e); throw new RepositoryException(msg, e); } } /** * @see JackrabbitSecurityManager#getPrincipalManager(Session) */ public PrincipalManager getPrincipalManager(Session session) throws RepositoryException { checkInitialized(); if (session instanceof SessionImpl) { SessionImpl sImpl = (SessionImpl) session; return createPrincipalManager(sImpl); } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserManager(Session) */ public UserManager getUserManager(Session session) throws RepositoryException { checkInitialized(); if (session == systemSession) { return systemUserManager; } else if (session instanceof SessionImpl) { String workspaceName = systemSession.getWorkspace().getName(); try { SessionImpl sImpl = (SessionImpl) session; UserManagerImpl uMgr; if (workspaceName.equals(sImpl.getWorkspace().getName())) { uMgr = createUserManager(sImpl); } else { SessionImpl s = (SessionImpl) sImpl.createSession(workspaceName); uMgr = createUserManager(s); sImpl.addListener(uMgr); } return uMgr; } catch (NoSuchWorkspaceException e) { throw new AccessControlException("Cannot build UserManager for " + session.getUserID(), e); } } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserID(javax.security.auth.Subject, String) */ public String getUserID(Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); // 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(); if (cl != null) { Set s = subject.getPrincipals(cl); if (!s.isEmpty()) { for (Principal p : s) { if (!GroupPrincipals.isGroup(p)) { return p.getName(); } } // all principals found with the given p-Class were Group principals log.debug("Only Group principals found with class '" + cl.getName() + "' -> Not used for UserID."); } else { log.debug("No principal found with class '" + cl.getName() + "'."); } } /* Fallback scenario to retrieve userID from the subject: Since the subject may contain multiple principals and the principal name may not be equals to the UserID, the id is retrieved by searching for the corresponding authorizable and if this doesn't succeed an attempt is made to obtained it from the login-credentials. */ String uid = null; // first try to retrieve an authorizable corresponding to // a non-group principal. the first one present is used // to determine the userID. try { UserManager umgr = getSystemUserManager(workspaceName); for (Principal p : subject.getPrincipals()) { if (!(p instanceof Group)) { Authorizable authorz = umgr.getAuthorizable(p); if (authorz != null && !authorz.isGroup()) { uid = authorz.getID(); break; } } } } catch (RepositoryException e) { // failed to access userid via user manager -> use fallback 2. log.error("Unexpected error while retrieving UserID.", e); } // 2. if no matching user is found try simple access to userID over // SimpleCredentials. if (uid == null) { Iterator creds = subject.getPublicCredentials( SimpleCredentials.class).iterator(); if (creds.hasNext()) { SimpleCredentials sc = creds.next(); uid = sc.getUserID(); } } return uid; } /** * Creates an AuthContext for the given {@link Credentials} and * {@link Subject}. The workspace name is ignored and users are * stored and retrieved from a specific (separate) workspace. * This includes selection of application specific LoginModules and * initialization with credentials and Session to System-Workspace * * @return an {@link AuthContext} for the given Credentials, Subject * @throws RepositoryException in other exceptional repository states */ public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); return getAuthContextProvider().getAuthContext(creds, subject, systemSession, getPrincipalProviderRegistry(), adminId, anonymousId); } //----------------------------------------------------------< protected >--- /** * @return The SecurityManagerConfig configured for the * repository this manager has been created for. */ protected SecurityManagerConfig getConfig() { return repository.getConfig().getSecurityConfig().getSecurityManagerConfig(); } /** * @param workspaceName The name of the target workspace. * @return The system user manager. Since this implementation stores users * in a dedicated workspace the system user manager is the same for all * sessions irrespective of the workspace. * @throws javax.jcr.RepositoryException If an error occurs. */ protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException { return systemUserManager; } /** * @param session The session for which to retrieve the membership cache. * @return The membership cache. * @throws RepositoryException If an error occurs. */ protected MembershipCache getMembershipCache(SessionImpl session) throws RepositoryException { if (session == systemSession || session instanceof SystemSession) { // force creation of the membership cache within the corresponding uMgr return null; } else { return ((UserManagerImpl) getSystemUserManager(session.getWorkspace().getName())).getMembershipCache(); } } /** * Creates a {@link UserManagerImpl} for the given session. May be overridden * to return a custom implementation. * * @param session session * @return user manager * @throws RepositoryException if an error occurs */ protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException { UserManagerConfig umc = getConfig().getUserManagerConfig(); UserManagerImpl um; if (umc != null) { Class>[] paramTypes = new Class[] { SessionImpl.class, String.class, Properties.class, MembershipCache.class}; um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class, paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session)); } else { um = new UserManagerImpl(session, adminId, null, getMembershipCache(session)); } if (umc != null && !(session instanceof SystemSession)) { AuthorizableAction[] actions = umc.getAuthorizableActions(); um.setAuthorizableActions(actions); } return um; } /** * @param session The session used to create the principal manager. * @return A new instance of PrincipalManagerImpl * @throws javax.jcr.RepositoryException If an error occurs. */ protected PrincipalManager createPrincipalManager(SessionImpl session) throws RepositoryException { return new PrincipalManagerImpl(session, getPrincipalProviderRegistry().getProviders()); } /** * @return A nwe instance of WorkspaceAccessManagerImpl to be used as * default workspace access manager if the configuration doesn't specify one. */ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { return new WorkspaceAccessManagerImpl(); } /** * Creates the default principal provider used to create the * {@link PrincipalProviderRegistry}. * * @return An new instance of DefaultPrincipalProvider. * @throws RepositoryException If an error occurs. */ protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { boolean initialized = false; PrincipalProvider defaultPP = new DefaultPrincipalProvider(this.systemSession, (UserManagerImpl) systemUserManager); 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; } /** * @return The PrincipalProviderRegistry created during initialization. */ protected PrincipalProviderRegistry getPrincipalProviderRegistry() { return principalProviderRegistry; } /** * @return The AuthContextProvider created during initialization. */ protected AuthContextProvider getAuthContextProvider() { return authContextProvider; } /** * Throws IllegalStateException if this manager hasn't been * initialized. */ protected void checkInitialized() { if (!initialized) { throw new IllegalStateException("Not initialized"); } } /** * @return The system session used to initialize this SecurityManager. */ protected Session getSystemSession() { return systemSession; } /** * @return The repository used to initialize this SecurityManager. */ protected Repository getRepository() { return repository; } //-------------------------------------------------------------------------- /** * Returns the access control provider for the specified * workspaceName. * * @param workspaceName Name of the workspace. * @return access control provider * @throws NoSuchWorkspaceException If no workspace with 'workspaceName' exists. * @throws RepositoryException */ private AccessControlProvider getAccessControlProvider(String workspaceName) throws NoSuchWorkspaceException, RepositoryException { checkInitialized(); AccessControlProvider provider = acProviders.get(workspaceName); if (provider == null || !provider.isLive()) { // mark this workspace as 'active' so the workspace does not // get disposed by the workspace-janitor // TODO: There should be a cleaner way to do this. repository.markWorkspaceActive(workspaceName); WorkspaceSecurityConfig secConf = null; WorkspaceConfig conf = repository.getConfig().getWorkspaceConfig(workspaceName); if (conf != null) { secConf = conf.getSecurityConfig(); } provider = acProviderFactory.createProvider( repository.getSystemSession(workspaceName), secConf); synchronized (acProviders) { acProviders.put(workspaceName, provider); } } return provider; } /** * Make sure the system users (admin and anonymous) exist. * * @param userManager Manager to create users/groups. * @param session The editing session. * @param adminId UserID of the administrator. * @param anonymousId UserID of the anonymous user. * @throws RepositoryException If an error occurs. */ static void createSystemUsers(UserManager userManager, SystemSession session, String adminId, String anonymousId) throws RepositoryException { Authorizable admin; if (adminId != null) { admin = userManager.getAuthorizable(adminId); if (admin == null) { userManager.createUser(adminId, adminId); if (!userManager.isAutoSave()) { session.save(); } log.info("... created admin-user with id \'" + adminId + "\' ..."); } } if (anonymousId != null) { Authorizable anonymous = userManager.getAuthorizable(anonymousId); if (anonymous == null) { try { userManager.createUser(anonymousId, ""); if (!userManager.isAutoSave()) { session.save(); } log.info("... created anonymous user with id \'" + anonymousId + "\' ..."); } catch (RepositoryException e) { // exception while creating the anonymous user. // log an error but don't abort the repository start-up log.error("Failed to create anonymous user.", e); } } } } //------------------------------------------------------< inner classes >--- /** * WorkspaceAccessManager that upon {@link #grants(Set principals, String)} * evaluates if access to the root node of a workspace with the specified * name is granted. */ private final class WorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { //-----------------------------------------< WorkspaceAccessManager >--- /** * {@inheritDoc} */ public void init(Session systemSession) throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public void close() throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public boolean grants(Set principals, String workspaceName) throws RepositoryException { AccessControlProvider prov = getAccessControlProvider(workspaceName); return prov.canAccessRoot(principals); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ItemNotFoundException; import javax.jcr.RepositoryException; 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.spi.Name; import org.apache.jackrabbit.spi.Path; /** * The HierarchyManager interface ... */ public interface HierarchyManager { /** * Resolves a path into an item id. * * If there is both a node and a property at the specified path, this method * will return the id of the node. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * item to be found at path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #resolveNodePath(Path)} and * {@link #resolvePropertyPath(Path)} should be used instead. * * @param path path to resolve * @return item id referred to by path or null * if there's no item at path. * @throws RepositoryException if an error occurs */ @Deprecated ItemId resolvePath(Path path) throws RepositoryException; /** * Resolves a path into a node id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * node to be found at path. * * @param path path to resolve * @return node id referred to by path or null * if there's no node at path. * @throws RepositoryException if an error occurs */ NodeId resolveNodePath(Path path) throws RepositoryException; /** * Resolves a path into a property id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * property to be found at path. * * @param path path to resolve * @return property id referred to by path or null * if there's no property at path. * @throws RepositoryException if an error occurs */ PropertyId resolvePropertyPath(Path path) throws RepositoryException; /** * Returns the path to the given item. * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item. * @param id id of item whose name should be returned * @return * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item, with the given parent id. If the * given item is not shareable, this is identical to {@link #getName(ItemId)}. * * @param id node id * @param parentId parent node id * @return name * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified item which is equivalent to * getPath(id).getAncestorCount(). The depth reflects the * absolute hierarchy level. * * @param id item id * @return the depth of the specified item * @throws ItemNotFoundException if the specified id does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified descendant relative to the given * ancestor. If ancestorId and descendantId * denote the same item 0 is returned. If ancestorId does not * denote an ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestorId does not * denote an ancestor of the item denoted by descendantId * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; /** * Determines whether the node with the specified nodeId * is an ancestor of the item denoted by the given itemId. * This is equivalent to * getPath(nodeId).isAncestorOf(getPath(itemId)). * * @param nodeId node id * @param itemId item id * @return true if the node with the specified * nodeId is an ancestor of the item denoted by the * given itemId; false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException; //------------------------------------------- operation with shareable nodes /** * Determines whether the node with the specified ancestor * is a share ancestor of the item denoted by the given descendant. * This is true for two nodes A, B * if either: * * A is a (proper) ancestor of B * there is a non-empty sequence of nodes N1,... * ,Nk such that A= * N1 and B=Nk * and Ni is the parent or a share-parent of * Ni+1 (for every i in 1 * ...k-1. * * * @param ancestor node id * @param descendant item id * @return true if the node denoted by ancestor * is a share ancestor of the item denoted by descendant, * false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified share-descendant relative to the given * share-ancestor. If ancestor and descendant * denote the same item, 0 is returned. If ancestor * does not denote an share-ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestor does * not denote a share-ancestor of the item denoted by descendant * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getShareRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; 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.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; 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.PropertyState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HierarchyManagerImpl ... */ public class HierarchyManagerImpl implements HierarchyManager { private static Logger log = LoggerFactory.getLogger(HierarchyManagerImpl.class); /** * The parent name returned for orphaned or root nodes. * TODO: Is it proper to use an invalid Name for this. */ private static final Name EMPTY_NAME = NameFactoryImpl.getInstance().create("", ""); protected final NodeId rootNodeId; protected final ItemStateManager provider; /** * Flags describing what items to return in {@link #resolvePath(Path, int)}. */ static final int RETURN_NODE = 1; static final int RETURN_PROPERTY = 2; static final int RETURN_ANY = (RETURN_NODE | RETURN_PROPERTY); public HierarchyManagerImpl(NodeId rootNodeId, ItemStateManager provider) { this.rootNodeId = rootNodeId; this.provider = provider; } public NodeId getRootNodeId() { return rootNodeId; } //-------------------------------------------------------< implementation > /** * Internal implementation that iteratively resolves a path into an item. * * @param elements path elements * @param next index of next item in elements to inspect * @param id id of item at path elements[0]..elements[next - 1] * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws ItemStateException if an intermediate item state is not found * @throws MalformedPathException if building an intermediate path fails */ protected ItemId resolvePath(Path.Element[] elements, int next, ItemId id, int typesAllowed) throws ItemStateException, MalformedPathException { PathBuilder builder = new PathBuilder(); for (int i = 0; i < next; i++) { builder.addLast(elements[i]); } for (int i = next; i < elements.length; i++) { Path.Element elem = elements[i]; NodeId parentId = (NodeId) id; id = null; Name name = elem.getName(); int index = elem.getIndex(); if (index == 0) { index = 1; } int typeExpected = typesAllowed; if (i < elements.length - 1) { // intermediate items must always be nodes typeExpected = RETURN_NODE; } NodeState parentState = (NodeState) getItemState(parentId); if ((typeExpected & RETURN_NODE) != 0) { ChildNodeEntry nodeEntry = getChildNodeEntry(parentState, name, index); if (nodeEntry != null) { id = nodeEntry.getId(); } } if (id == null && (typeExpected & RETURN_PROPERTY) != 0) { if (parentState.hasPropertyName(name) && (index <= 1)) { // property id = new PropertyId(parentState.getNodeId(), name); } } if (id == null) { break; } builder.addLast(elements[i]); pathResolved(id, builder); } return id; } //---------------------------------------------------------< overridables > /** * Return an item state, given its item id. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return item state * @throws NoSuchItemStateException if the item does not exist * @throws ItemStateException if an error occurs * @see ZombieHierarchyManager#getItemState(ItemId) */ protected ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { return provider.getItemState(id); } /** * Determines whether an item state for a given item id exists. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return true if an item state exists, otherwise * false * @see ZombieHierarchyManager#hasItemState(ItemId) */ protected boolean hasItemState(ItemId id) { return provider.hasItemState(id); } /** * Returns the parentUUID of the given item. * * Low-level hook provided for specialized derived classes. * * @param state item state * @return parentUUID of the given item * @see ZombieHierarchyManager#getParentId(ItemState) */ protected NodeId getParentId(ItemState state) { return state.getParentId(); } /** * Return all parents of a node. A shareable node has possibly more than * one parent. * * @param state item state * @param useOverlayed whether to use overlayed state for shareable nodes * @return set of parent NodeIds. If state has no parent, * array has length 0. */ protected Set getParentIds(ItemState state, boolean useOverlayed) { if (state.isNode()) { // if this is a node, quickly check whether it is shareable and // whether it contains more than one parent NodeState ns = (NodeState) state; if (ns.isShareable() && useOverlayed && ns.hasOverlayedState()) { ns = (NodeState) ns.getOverlayedState(); } Set s = ns.getSharedSet(); if (s.size() > 1) { return s; } } NodeId parentId = getParentId(state); if (parentId != null) { LinkedHashSet s = new LinkedHashSet(); s.add(parentId); return s; } return Collections.emptySet(); } /** * Returns the ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
modified
* Iterate over all cached children of this state and verify each * child's position. */ public void nodesReplaced(NodeState state) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(state.getNodeId()); if (entry == null) { return; } for (PathMap.Element parent : entry.getElements()) { HashMap> newChildrenOrder = new HashMap>(); boolean orderChanged = false; for (PathMap.Element child : parent.getChildren()) { LRUEntry childEntry = child.get(); if (childEntry == null) { // Child has no associated UUID information: we're // therefore unable to determine if this child's // position is still accurate and have to assume // the worst and remove it. evict(child, false); } else { NodeId childId = childEntry.getId(); ChildNodeEntry cne = state.getChildNodeEntry(childId); if (cne == null) { // Child no longer in parent node, so remove it evict(child, false); } else { // Put all children into map of new children order // - regardless whether their position changed or // not - as we might need to reorder them later on. Path.Element newNameIndex = PathFactoryImpl.getInstance().createElement( cne.getName(), cne.getIndex()); newChildrenOrder.put(newNameIndex, child); if (!newNameIndex.equals(child.getPathElement())) { orderChanged = true; } } } } if (orderChanged) { /* If at least one child changed its position, reorder */ parent.setChildren(newChildrenOrder); } } checkConsistency(); } } /** * {@inheritDoc} */ public void nodeRemoved(NodeState state, Name name, int index, NodeId id) { 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); checkConsistency(); } catch (PathNotFoundException e) { log.warn("Unable to get path of node " + state.getNodeId() + ", event ignored."); } catch (MalformedPathException e) { log.warn("Unable to create path of " + id, e); } catch (ItemStateException e) { log.warn("Unable to find item " + id, e); } catch (ItemNotFoundException e) { log.warn("Unable to get path of " + state.getNodeId(), e); } 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); } } } //------------------------------------------------------< private methods > /** * Return the first cached path that is mapped to given id. * * @param id node id * @return cached element, null if not found */ private PathMap.Element get(ItemId id) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { entry.touch(); return entry.getElements()[0]; } return null; } } /** * Return the nearest cached element in the path map, given a path. * The returned element is guaranteed to have an associated object that * is not null. * * @param path path * @return cached element, null if not found */ private PathMap.Element map(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, false); while (element != null) { LRUEntry entry = element.get(); if (entry != null) { entry.touch(); return element; } element = element.getParent(); } return null; } } /** * Cache an item in the hierarchy given its id and path. * * @param id node id * @param path path to item */ private void cache(NodeId id, Path path) { synchronized (cacheMonitor) { if (isCached(id, path)) { return; } if (idCache.size() >= upperLimit) { idCacheStatistics.log(); /** * Remove least recently used item. Scans the LRU list from * head to tail and removes the first item that has no children. */ LRUEntry entry = head; while (entry != null) { PathMap.Element[] elements = entry.getElements(); int childrenCount = 0; for (int i = 0; i < elements.length; i++) { childrenCount += elements[i].getChildrenCount(); } if (childrenCount == 0) { evictAll(entry.getId(), false); return; } entry = entry.getNext(); } } PathMap.Element element = pathCache.put(path); if (element.get() != null) { if (!id.equals((element.get()).getId())) { log.debug("overwriting PathMap.Element"); } } LRUEntry entry = idCache.get(id); if (entry == null) { entry = new LRUEntry(id, element); idCache.put(id, entry); } else { entry.addElement(element); } element.set(entry); checkConsistency(); } } /** * Return a flag indicating whether a certain node and/or path is cached. * If path is null, check whether the item is * cached at all. If path is not null, * check whether the item is cached with that path. * * @param id item id * @param path path, may be null * @return true if the item is already cached; * false otherwise */ boolean isCached(NodeId id, Path path) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry == null) { return false; } if (path == null) { return true; } PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i].hasPath(path)) { return true; } } return false; } } /** * Return a flag indicating whether a certain path is cached. * * @param path item path * @return true if the item is already cached; * false otherwise */ boolean isCached(Path path) { synchronized (cacheMonitor) { PathMap.Element element = pathCache.map(path, true); if (element != null) { return element.get() != null; } return false; } } /** * Remove all path mapping for a given item id. Removes the associated * LRUEntry and the PathMap.Element with it. * Indexes of same name sibling elements are shifted! * * @param id item id */ private void evictAll(ItemId id, boolean shift) { synchronized (cacheMonitor) { LRUEntry entry = idCache.get(id); if (entry != null) { PathMap.Element[] elements = entry.getElements(); for (int i = 0; i < elements.length; i++) { evict(elements[i], shift); } } checkConsistency(); } } /** * Evict path map element from cache. This will traverse all children * of this element and remove the objects associated with them. * Index of same name sibling items are shifted! * * @param element path map element */ private void evict(PathMap.Element element, boolean shift) { // assert: synchronized (cacheMonitor) element.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { LRUEntry entry = element.get(); if (entry.removeElement(element) == 0) { idCache.remove(entry.getId()); entry.remove(); } } }, false); element.remove(shift); } /** * Invoked when a notification about a child node addition has been received. * * @param state node state where child was added * @param path path to child node * @param id child node id * * @throws PathNotFoundException if the path was not found * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeAdded(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element element = null; LRUEntry entry = idCache.get(id); if (entry != null) { // child node already cached: this can have the following // reasons: // 1) node was moved, cached path is outdated // 2) node was cloned, cached path is still valid NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { PathMap.Element[] elements = entry.getElements(); element = elements[0]; for (int i = 0; i < elements.length; i++) { elements[i].remove(); } } } PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent != null) { parent.insert(path.getNameElement()); } if (element != null) { // store remembered element at new position pathCache.put(path, element); } } /** * Invoked when a notification about a child node removal has been received. * * @param state node state * @param path node path * @param id node id * * @throws PathNotFoundException if the path was not found. * @throws RepositoryException If the path's direct ancestor cannot be determined. * @throws ItemStateException If the id cannot be resolved to a NodeState. */ private void nodeRemoved(NodeState state, Path path, NodeId id) throws RepositoryException, ItemStateException { // assert: synchronized (cacheMonitor) PathMap.Element parent = pathCache.map(path.getAncestor(1), true); if (parent == null) { return; } PathMap.Element element = parent.getDescendant(path.getLastElement(), true); if (element != null) { // with SNS, this might evict a child that is NOT the one // having id, check first whether item has // the id passed as argument LRUEntry entry = element.get(); if (entry != null && !entry.getId().equals(id)) { return; } // if item is shareable, remove this path only, otherwise // every path this item has been mapped to NodeState child = null; if (hasItemState(id)) { child = (NodeState) getItemState(id); } if (child == null || !child.isShareable()) { evictAll(id, true); } else { evict(element, true); } } else { // element itself is not cached, but removal might cause SNS // index shifting parent.remove(path.getNameElement()); } } /** * Dump contents of path map and elements included to a string. */ public String toString() { final StringBuilder builder = new StringBuilder(); synchronized (cacheMonitor) { pathCache.traverse(new PathMap.ElementVisitor() { public void elementVisited(PathMap.Element element) { for (int i = 0; i < element.getDepth(); i++) { builder.append("--"); } builder.append(element.getName()); int index = element.getIndex(); if (index != 0 && index != 1) { builder.append('['); builder.append(index); builder.append(']'); } builder.append(" "); builder.append(element.get()); builder.append("\n"); } }, true); } return builder.toString(); } /** * Check consistency. */ private void checkConsistency() throws IllegalStateException { // assert: synchronized (cacheMonitor) if (!consistencyCheckEnabled) { return; } int elementsInCache = 0; Iterator iter = idCache.values().iterator(); while (iter.hasNext()) { LRUEntry entry = iter.next(); elementsInCache += entry.getElements().length; } class PathMapElementCounter implements PathMap.ElementVisitor { int count; public void elementVisited(PathMap.Element element) { LRUEntry mappedEntry = element.get(); LRUEntry cachedEntry = idCache.get(mappedEntry.getId()); if (cachedEntry == null) { String msg = "Path element (" + element + " ) cached in path map, associated id (" + mappedEntry.getId() + ") isn't."; throw new IllegalStateException(msg); } if (cachedEntry != mappedEntry) { String msg = "LRUEntry associated with element (" + element + " ) in path map is not equal to cached LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } PathMap.Element[] elements = cachedEntry.getElements(); for (int i = 0; i < elements.length; i++) { if (elements[i] == element) { count++; return; } } String msg = "Element (" + element + ") cached in path map, but not in associated LRUEntry (" + cachedEntry.getId() + ")."; throw new IllegalStateException(msg); } } PathMapElementCounter counter = new PathMapElementCounter(); pathCache.traverse(counter, false); if (counter.count != elementsInCache) { String msg = "PathMap element and cached element count don't match (" + counter.count + " != " + elementsInCache + ")"; throw new IllegalStateException(msg); } } /** * 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 { /** * Previous entry */ private LRUEntry previous; /** * Next entry */ private LRUEntry next; /** * Node id */ private final NodeId id; /** * Elements in path map */ private PathMap.Element[] elements; /** * Create a new instance of this class * * @param id node id * @param element the path map element for this entry */ @SuppressWarnings("unchecked") public LRUEntry(NodeId id, PathMap.Element element) { this.id = id; this.elements = new PathMap.Element[] { element }; append(); } /** * Append entry to end of LRU list */ public void append() { if (tail == null) { head = this; tail = this; } else { previous = tail; tail.next = this; tail = this; } } /** * Remove entry from LRU list */ public void remove() { if (previous != null) { previous.next = next; } if (next != null) { next.previous = previous; } if (head == this) { head = next; } if (tail == this) { tail = previous; } previous = null; next = null; } /** * Touch entry. Removes it from its current position in the LRU list * and moves it to the end. */ public void touch() { remove(); append(); } /** * Return next LRU entry * * @return next LRU entry */ public LRUEntry getNext() { return next; } /** * Return node ID * * @return node ID */ public NodeId getId() { return id; } /** * Return elements in path map that are mapped to id. If * this entry is a shareable node or one of its descendant, it can * be reached by more than one path. * * @return element in path map */ public PathMap.Element[] getElements() { return elements; } /** * Add a mapping to some element. */ @SuppressWarnings("unchecked") public void addElement(PathMap.Element element) { PathMap.Element[] tmp = new PathMap.Element[elements.length + 1]; System.arraycopy(elements, 0, tmp, 0, elements.length); tmp[elements.length] = element; elements = tmp; } /** * Remove a mapping to some element from this entry. * * @return number of mappings left */ @SuppressWarnings("unchecked") public int removeElement(PathMap.Element element) { boolean found = false; for (int i = 0; i < elements.length; i++) { if (found) { elements[i - 1] = elements[i]; } else if (elements[i] == element) { found = true; } } if (found) { PathMap.Element[] tmp = new PathMap.Element[elements.length - 1]; System.arraycopy(elements, 0, tmp, 0, tmp.length); elements = tmp; } return elements.length; } /** * {@inheritDoc} */ public String toString() { return id.toString(); } } private final class CacheStatistics { private final String id; private final ReferenceMap cache; private long timeStamp = 0; public CacheStatistics() { this.id = cacheMonitor.toString(); this.cache = idCache; } public void log() { if (log.isDebugEnabled()) { long now = System.currentTimeMillis(); final String msg = "Cache id = {};size = {};max = {}"; if (log.isTraceEnabled()) { log.trace(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } else if (now > timeStamp + CACHE_STATISTICS_LOG_INTERVAL_MILLIS) { timeStamp = now; log.debug(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception()); } } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.security.Principal; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.security.AccessControlException; import javax.security.auth.Subject; import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.core.config.AccessManagerConfig; import org.apache.jackrabbit.core.config.LoginModuleConfig; import org.apache.jackrabbit.core.config.SecurityConfig; import org.apache.jackrabbit.core.config.SecurityManagerConfig; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; import org.apache.jackrabbit.core.config.UserManagerConfig; import org.apache.jackrabbit.core.security.AMContext; 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.GroupPrincipals; import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl; import org.apache.jackrabbit.core.security.principal.PrincipalProvider; import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl; import org.apache.jackrabbit.core.security.user.MembershipCache; import org.apache.jackrabbit.core.security.user.UserManagerImpl; import org.apache.jackrabbit.core.security.user.action.AuthorizableAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The security manager acts as central managing class for all security related * operations on a low-level non-protected level. It manages the * * {@link PrincipalProvider}s * {@link AccessControlProvider}s * {@link WorkspaceAccessManager} * {@link UserManager} * */ public class DefaultSecurityManager implements JackrabbitSecurityManager { /** * the default logger */ private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class); /** * Flag indicating if the security manager was properly initialized. */ private boolean initialized; /** * the repository implementation */ private RepositoryImpl repository; /** * System session. */ private SystemSession systemSession; /** * System user manager. Implementation needed here for the DefaultPrincipalProvider. */ private UserManager systemUserManager; /** * The user id of the administrator. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ADMIN_ID}). */ protected String adminId; /** * The user id of the anonymous user. The value is retrieved from * configuration. If the config entry is missing a default id is used (see * {@link SecurityConstants#ANONYMOUS_ID}). */ protected String anonymousId; /** * Contains the access control providers per workspace. * key = name of the workspace, * value = {@link AccessControlProvider} */ private final Map acProviders = new HashMap(); /** * the AccessControlProviderFactory */ private AccessControlProviderFactory acProviderFactory; /** * the configured WorkspaceAccessManager */ private WorkspaceAccessManager workspaceAccessManager; /** * the principal provider registry */ private PrincipalProviderRegistry principalProviderRegistry; /** * factory for login-context {@see Repository#login()) */ private AuthContextProvider authContextProvider; //------------------------------------------< JackrabbitSecurityManager >--- /** * @see JackrabbitSecurityManager#init(Repository, Session) */ public synchronized void init(Repository repository, Session systemSession) throws RepositoryException { if (initialized) { throw new IllegalStateException("already initialized"); } if (!(repository instanceof RepositoryImpl)) { throw new RepositoryException("RepositoryImpl expected"); } if (!(systemSession instanceof SystemSession)) { throw new RepositoryException("SystemSession expected"); } this.systemSession = (SystemSession) systemSession; this.repository = (RepositoryImpl) repository; SecurityConfig config = this.repository.getConfig().getSecurityConfig(); LoginModuleConfig loginModConf = config.getLoginModuleConfig(); // build AuthContextProvider based on appName + optional LoginModuleConfig authContextProvider = new AuthContextProvider(config.getAppName(), loginModConf); if (authContextProvider.isLocal()) { log.info("init: use Repository Login-Configuration for " + config.getAppName()); } else if (authContextProvider.isJAAS()) { log.info("init: use JAAS login-configuration for " + config.getAppName()); } else { String msg = "Neither JAAS nor RepositoryConfig contained a valid configuration for " + config.getAppName(); log.error(msg); throw new RepositoryException(msg); } Properties[] moduleConfig = authContextProvider.getModuleConfig(); // retrieve default-ids (admin and anonymous) from login-module-configuration. for (Properties props : moduleConfig) { if (props.containsKey(LoginModuleConfig.PARAM_ADMIN_ID)) { adminId = props.getProperty(LoginModuleConfig.PARAM_ADMIN_ID); } if (props.containsKey(LoginModuleConfig.PARAM_ANONYMOUS_ID)) { anonymousId = props.getProperty(LoginModuleConfig.PARAM_ANONYMOUS_ID); } } // fallback: if (adminId == null) { log.debug("No adminID defined in LoginModule/JAAS config -> using default."); adminId = SecurityConstants.ADMIN_ID; } if (anonymousId == null) { log.debug("No anonymousID defined in LoginModule/JAAS config -> using default."); anonymousId = SecurityConstants.ANONYMOUS_ID; } // create the system userManager and make sure the system-users exist. systemUserManager = createUserManager(this.systemSession); createSystemUsers(systemUserManager, this.systemSession, adminId, anonymousId); // init default ac-provider-factory acProviderFactory = new AccessControlProviderFactoryImpl(); acProviderFactory.init(this.systemSession); // create the workspace access manager SecurityManagerConfig smc = config.getSecurityManagerConfig(); if (smc != null && smc.getWorkspaceAccessConfig() != null) { workspaceAccessManager = smc.getWorkspaceAccessConfig().newInstance(WorkspaceAccessManager.class); } else { // fallback -> the default implementation log.debug("No WorkspaceAccessManager configured; using default."); workspaceAccessManager = createDefaultWorkspaceAccessManager(); } workspaceAccessManager.init(this.systemSession); // initialize principal-provider registry // 1) create default PrincipalProvider defaultPP = createDefaultPrincipalProvider(moduleConfig); // 2) create registry instance principalProviderRegistry = new ProviderRegistryImpl(defaultPP); // 3) register all configured principal providers. for (Properties props : moduleConfig) { principalProviderRegistry.registerProvider(props); } initialized = true; } /** * @see JackrabbitSecurityManager#dispose(String) */ public void dispose(String workspaceName) { checkInitialized(); synchronized (acProviders) { AccessControlProvider prov = acProviders.remove(workspaceName); if (prov != null) { prov.close(); } } } /** * @see JackrabbitSecurityManager#close() */ public void close() { checkInitialized(); synchronized (acProviders) { for (AccessControlProvider accessControlProvider : acProviders.values()) { accessControlProvider.close(); } acProviders.clear(); } } /** * @see JackrabbitSecurityManager#getAccessManager(Session,AMContext) */ public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { checkInitialized(); AccessManagerConfig amConfig = repository.getConfig().getSecurityConfig().getAccessManagerConfig(); try { String wspName = session.getWorkspace().getName(); AccessControlProvider pp = getAccessControlProvider(wspName); AccessManager accessMgr; if (amConfig == null) { log.debug("No configuration entry for AccessManager. Using org.apache.jackrabbit.core.security.DefaultAccessManager"); accessMgr = new DefaultAccessManager(); } else { accessMgr = amConfig.newInstance(AccessManager.class); } accessMgr.init(amContext, pp, workspaceAccessManager); return accessMgr; } catch (AccessDeniedException e) { // re-throw throw e; } catch (Exception e) { // wrap in RepositoryException String clsName = (amConfig == null) ? "-- missing access manager configuration --" : amConfig.getClassName(); String msg = "Failed to instantiate AccessManager (" + clsName + ")"; log.error(msg, e); throw new RepositoryException(msg, e); } } /** * @see JackrabbitSecurityManager#getPrincipalManager(Session) */ public PrincipalManager getPrincipalManager(Session session) throws RepositoryException { checkInitialized(); if (session instanceof SessionImpl) { SessionImpl sImpl = (SessionImpl) session; return createPrincipalManager(sImpl); } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserManager(Session) */ public UserManager getUserManager(Session session) throws RepositoryException { checkInitialized(); if (session == systemSession) { return systemUserManager; } else if (session instanceof SessionImpl) { String workspaceName = systemSession.getWorkspace().getName(); try { SessionImpl sImpl = (SessionImpl) session; UserManagerImpl uMgr; if (workspaceName.equals(sImpl.getWorkspace().getName())) { uMgr = createUserManager(sImpl); } else { SessionImpl s = (SessionImpl) sImpl.createSession(workspaceName); uMgr = createUserManager(s); sImpl.addListener(uMgr); } return uMgr; } catch (NoSuchWorkspaceException e) { throw new AccessControlException("Cannot build UserManager for " + session.getUserID(), e); } } else { throw new RepositoryException("Internal error: SessionImpl expected."); } } /** * @see JackrabbitSecurityManager#getUserID(javax.security.auth.Subject, String) */ public String getUserID(Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); // 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(); if (cl != null) { Set s = subject.getPrincipals(cl); if (!s.isEmpty()) { for (Principal p : s) { if (!GroupPrincipals.isGroup(p)) { return p.getName(); } } // all principals found with the given p-Class were Group principals log.debug("Only Group principals found with class '" + cl.getName() + "' -> Not used for UserID."); } else { log.debug("No principal found with class '" + cl.getName() + "'."); } } /* Fallback scenario to retrieve userID from the subject: Since the subject may contain multiple principals and the principal name may not be equals to the UserID, the id is retrieved by searching for the corresponding authorizable and if this doesn't succeed an attempt is made to obtained it from the login-credentials. */ String uid = null; // first try to retrieve an authorizable corresponding to // a non-group principal. the first one present is used // to determine the userID. try { UserManager umgr = getSystemUserManager(workspaceName); for (Principal p : subject.getPrincipals()) { if (!(p instanceof Group)) { Authorizable authorz = umgr.getAuthorizable(p); if (authorz != null && !authorz.isGroup()) { uid = authorz.getID(); break; } } } } catch (RepositoryException e) { // failed to access userid via user manager -> use fallback 2. log.error("Unexpected error while retrieving UserID.", e); } // 2. if no matching user is found try simple access to userID over // SimpleCredentials. if (uid == null) { Iterator creds = subject.getPublicCredentials( SimpleCredentials.class).iterator(); if (creds.hasNext()) { SimpleCredentials sc = creds.next(); uid = sc.getUserID(); } } return uid; } /** * Creates an AuthContext for the given {@link Credentials} and * {@link Subject}. The workspace name is ignored and users are * stored and retrieved from a specific (separate) workspace. * This includes selection of application specific LoginModules and * initialization with credentials and Session to System-Workspace * * @return an {@link AuthContext} for the given Credentials, Subject * @throws RepositoryException in other exceptional repository states */ public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); return getAuthContextProvider().getAuthContext(creds, subject, systemSession, getPrincipalProviderRegistry(), adminId, anonymousId); } //----------------------------------------------------------< protected >--- /** * @return The SecurityManagerConfig configured for the * repository this manager has been created for. */ protected SecurityManagerConfig getConfig() { return repository.getConfig().getSecurityConfig().getSecurityManagerConfig(); } /** * @param workspaceName The name of the target workspace. * @return The system user manager. Since this implementation stores users * in a dedicated workspace the system user manager is the same for all * sessions irrespective of the workspace. * @throws javax.jcr.RepositoryException If an error occurs. */ protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException { return systemUserManager; } /** * @param session The session for which to retrieve the membership cache. * @return The membership cache. * @throws RepositoryException If an error occurs. */ protected MembershipCache getMembershipCache(SessionImpl session) throws RepositoryException { if (session == systemSession || session instanceof SystemSession) { // force creation of the membership cache within the corresponding uMgr return null; } else { return ((UserManagerImpl) getSystemUserManager(session.getWorkspace().getName())).getMembershipCache(); } } /** * Creates a {@link UserManagerImpl} for the given session. May be overridden * to return a custom implementation. * * @param session session * @return user manager * @throws RepositoryException if an error occurs */ protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException { UserManagerConfig umc = getConfig().getUserManagerConfig(); UserManagerImpl um; if (umc != null) { Class>[] paramTypes = new Class[] { SessionImpl.class, String.class, Properties.class, MembershipCache.class}; um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class, paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session)); } else { um = new UserManagerImpl(session, adminId, null, getMembershipCache(session)); } if (umc != null && !(session instanceof SystemSession)) { AuthorizableAction[] actions = umc.getAuthorizableActions(); um.setAuthorizableActions(actions); } return um; } /** * @param session The session used to create the principal manager. * @return A new instance of PrincipalManagerImpl * @throws javax.jcr.RepositoryException If an error occurs. */ protected PrincipalManager createPrincipalManager(SessionImpl session) throws RepositoryException { return new PrincipalManagerImpl(session, getPrincipalProviderRegistry().getProviders()); } /** * @return A nwe instance of WorkspaceAccessManagerImpl to be used as * default workspace access manager if the configuration doesn't specify one. */ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { return new WorkspaceAccessManagerImpl(); } /** * Creates the default principal provider used to create the * {@link PrincipalProviderRegistry}. * * @return An new instance of DefaultPrincipalProvider. * @throws RepositoryException If an error occurs. */ protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { boolean initialized = false; PrincipalProvider defaultPP = new DefaultPrincipalProvider(this.systemSession, (UserManagerImpl) systemUserManager); 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; } /** * @return The PrincipalProviderRegistry created during initialization. */ protected PrincipalProviderRegistry getPrincipalProviderRegistry() { return principalProviderRegistry; } /** * @return The AuthContextProvider created during initialization. */ protected AuthContextProvider getAuthContextProvider() { return authContextProvider; } /** * Throws IllegalStateException if this manager hasn't been * initialized. */ protected void checkInitialized() { if (!initialized) { throw new IllegalStateException("Not initialized"); } } /** * @return The system session used to initialize this SecurityManager. */ protected Session getSystemSession() { return systemSession; } /** * @return The repository used to initialize this SecurityManager. */ protected Repository getRepository() { return repository; } //-------------------------------------------------------------------------- /** * Returns the access control provider for the specified * workspaceName. * * @param workspaceName Name of the workspace. * @return access control provider * @throws NoSuchWorkspaceException If no workspace with 'workspaceName' exists. * @throws RepositoryException */ private AccessControlProvider getAccessControlProvider(String workspaceName) throws NoSuchWorkspaceException, RepositoryException { checkInitialized(); AccessControlProvider provider = acProviders.get(workspaceName); if (provider == null || !provider.isLive()) { // mark this workspace as 'active' so the workspace does not // get disposed by the workspace-janitor // TODO: There should be a cleaner way to do this. repository.markWorkspaceActive(workspaceName); WorkspaceSecurityConfig secConf = null; WorkspaceConfig conf = repository.getConfig().getWorkspaceConfig(workspaceName); if (conf != null) { secConf = conf.getSecurityConfig(); } provider = acProviderFactory.createProvider( repository.getSystemSession(workspaceName), secConf); synchronized (acProviders) { acProviders.put(workspaceName, provider); } } return provider; } /** * Make sure the system users (admin and anonymous) exist. * * @param userManager Manager to create users/groups. * @param session The editing session. * @param adminId UserID of the administrator. * @param anonymousId UserID of the anonymous user. * @throws RepositoryException If an error occurs. */ static void createSystemUsers(UserManager userManager, SystemSession session, String adminId, String anonymousId) throws RepositoryException { Authorizable admin; if (adminId != null) { admin = userManager.getAuthorizable(adminId); if (admin == null) { userManager.createUser(adminId, adminId); if (!userManager.isAutoSave()) { session.save(); } log.info("... created admin-user with id \'" + adminId + "\' ..."); } } if (anonymousId != null) { Authorizable anonymous = userManager.getAuthorizable(anonymousId); if (anonymous == null) { try { userManager.createUser(anonymousId, ""); if (!userManager.isAutoSave()) { session.save(); } log.info("... created anonymous user with id \'" + anonymousId + "\' ..."); } catch (RepositoryException e) { // exception while creating the anonymous user. // log an error but don't abort the repository start-up log.error("Failed to create anonymous user.", e); } } } } //------------------------------------------------------< inner classes >--- /** * WorkspaceAccessManager that upon {@link #grants(Set principals, String)} * evaluates if access to the root node of a workspace with the specified * name is granted. */ private final class WorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { //-----------------------------------------< WorkspaceAccessManager >--- /** * {@inheritDoc} */ public void init(Session systemSession) throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public void close() throws RepositoryException { // nothing to do here. } /** * {@inheritDoc} */ public boolean grants(Set principals, String workspaceName) throws RepositoryException { AccessControlProvider prov = getAccessControlProvider(workspaceName); return prov.canAccessRoot(principals); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ItemNotFoundException; import javax.jcr.RepositoryException; 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.spi.Name; import org.apache.jackrabbit.spi.Path; /** * The HierarchyManager interface ... */ public interface HierarchyManager { /** * Resolves a path into an item id. * * If there is both a node and a property at the specified path, this method * will return the id of the node. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * item to be found at path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #resolveNodePath(Path)} and * {@link #resolvePropertyPath(Path)} should be used instead. * * @param path path to resolve * @return item id referred to by path or null * if there's no item at path. * @throws RepositoryException if an error occurs */ @Deprecated ItemId resolvePath(Path path) throws RepositoryException; /** * Resolves a path into a node id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * node to be found at path. * * @param path path to resolve * @return node id referred to by path or null * if there's no node at path. * @throws RepositoryException if an error occurs */ NodeId resolveNodePath(Path path) throws RepositoryException; /** * Resolves a path into a property id. * * Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * property to be found at path. * * @param path path to resolve * @return property id referred to by path or null * if there's no property at path. * @throws RepositoryException if an error occurs */ PropertyId resolvePropertyPath(Path path) throws RepositoryException; /** * Returns the path to the given item. * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item. * @param id id of item whose name should be returned * @return * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item, with the given parent id. If the * given item is not shareable, this is identical to {@link #getName(ItemId)}. * * @param id node id * @param parentId parent node id * @return name * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified item which is equivalent to * getPath(id).getAncestorCount(). The depth reflects the * absolute hierarchy level. * * @param id item id * @return the depth of the specified item * @throws ItemNotFoundException if the specified id does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified descendant relative to the given * ancestor. If ancestorId and descendantId * denote the same item 0 is returned. If ancestorId does not * denote an ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestorId does not * denote an ancestor of the item denoted by descendantId * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; /** * Determines whether the node with the specified nodeId * is an ancestor of the item denoted by the given itemId. * This is equivalent to * getPath(nodeId).isAncestorOf(getPath(itemId)). * * @param nodeId node id * @param itemId item id * @return true if the node with the specified * nodeId is an ancestor of the item denoted by the * given itemId; false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException; //------------------------------------------- operation with shareable nodes /** * Determines whether the node with the specified ancestor * is a share ancestor of the item denoted by the given descendant. * This is true for two nodes A, B * if either: * * A is a (proper) ancestor of B * there is a non-empty sequence of nodes N1,... * ,Nk such that A= * N1 and B=Nk * and Ni is the parent or a share-parent of * Ni+1 (for every i in 1 * ...k-1. * * * @param ancestor node id * @param descendant item id * @return true if the node denoted by ancestor * is a share ancestor of the item denoted by descendant, * false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified share-descendant relative to the given * share-ancestor. If ancestor and descendant * denote the same item, 0 is returned. If ancestor * does not denote an share-ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestor does * not denote a share-ancestor of the item denoted by descendant * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getShareRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; 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.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; 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.PropertyState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HierarchyManagerImpl ... */ public class HierarchyManagerImpl implements HierarchyManager { private static Logger log = LoggerFactory.getLogger(HierarchyManagerImpl.class); /** * The parent name returned for orphaned or root nodes. * TODO: Is it proper to use an invalid Name for this. */ private static final Name EMPTY_NAME = NameFactoryImpl.getInstance().create("", ""); protected final NodeId rootNodeId; protected final ItemStateManager provider; /** * Flags describing what items to return in {@link #resolvePath(Path, int)}. */ static final int RETURN_NODE = 1; static final int RETURN_PROPERTY = 2; static final int RETURN_ANY = (RETURN_NODE | RETURN_PROPERTY); public HierarchyManagerImpl(NodeId rootNodeId, ItemStateManager provider) { this.rootNodeId = rootNodeId; this.provider = provider; } public NodeId getRootNodeId() { return rootNodeId; } //-------------------------------------------------------< implementation > /** * Internal implementation that iteratively resolves a path into an item. * * @param elements path elements * @param next index of next item in elements to inspect * @param id id of item at path elements[0]..elements[next - 1] * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws ItemStateException if an intermediate item state is not found * @throws MalformedPathException if building an intermediate path fails */ protected ItemId resolvePath(Path.Element[] elements, int next, ItemId id, int typesAllowed) throws ItemStateException, MalformedPathException { PathBuilder builder = new PathBuilder(); for (int i = 0; i < next; i++) { builder.addLast(elements[i]); } for (int i = next; i < elements.length; i++) { Path.Element elem = elements[i]; NodeId parentId = (NodeId) id; id = null; Name name = elem.getName(); int index = elem.getIndex(); if (index == 0) { index = 1; } int typeExpected = typesAllowed; if (i < elements.length - 1) { // intermediate items must always be nodes typeExpected = RETURN_NODE; } NodeState parentState = (NodeState) getItemState(parentId); if ((typeExpected & RETURN_NODE) != 0) { ChildNodeEntry nodeEntry = getChildNodeEntry(parentState, name, index); if (nodeEntry != null) { id = nodeEntry.getId(); } } if (id == null && (typeExpected & RETURN_PROPERTY) != 0) { if (parentState.hasPropertyName(name) && (index <= 1)) { // property id = new PropertyId(parentState.getNodeId(), name); } } if (id == null) { break; } builder.addLast(elements[i]); pathResolved(id, builder); } return id; } //---------------------------------------------------------< overridables > /** * Return an item state, given its item id. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return item state * @throws NoSuchItemStateException if the item does not exist * @throws ItemStateException if an error occurs * @see ZombieHierarchyManager#getItemState(ItemId) */ protected ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { return provider.getItemState(id); } /** * Determines whether an item state for a given item id exists. * * Low-level hook provided for specialized derived classes. * * @param id item id * @return true if an item state exists, otherwise * false * @see ZombieHierarchyManager#hasItemState(ItemId) */ protected boolean hasItemState(ItemId id) { return provider.hasItemState(id); } /** * Returns the parentUUID of the given item. * * Low-level hook provided for specialized derived classes. * * @param state item state * @return parentUUID of the given item * @see ZombieHierarchyManager#getParentId(ItemState) */ protected NodeId getParentId(ItemState state) { return state.getParentId(); } /** * Return all parents of a node. A shareable node has possibly more than * one parent. * * @param state item state * @param useOverlayed whether to use overlayed state for shareable nodes * @return set of parent NodeIds. If state has no parent, * array has length 0. */ protected Set getParentIds(ItemState state, boolean useOverlayed) { if (state.isNode()) { // if this is a node, quickly check whether it is shareable and // whether it contains more than one parent NodeState ns = (NodeState) state; if (ns.isShareable() && useOverlayed && ns.hasOverlayedState()) { ns = (NodeState) ns.getOverlayedState(); } Set s = ns.getSharedSet(); if (s.size() > 1) { return s; } } NodeId parentId = getParentId(state); if (parentId != null) { LinkedHashSet s = new LinkedHashSet(); s.add(parentId); return s; } return Collections.emptySet(); } /** * Returns the ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
path
PathMap.Element
id
SecurityManagerConfig
DefaultPrincipalProvider
IllegalStateException
workspaceName
WorkspaceAccessManager
* If there is both a node and a property at the specified path, this method * will return the id of the node. *
* Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * item to be found at path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #resolveNodePath(Path)} and * {@link #resolvePropertyPath(Path)} should be used instead. * * @param path path to resolve * @return item id referred to by path or null * if there's no item at path. * @throws RepositoryException if an error occurs */ @Deprecated ItemId resolvePath(Path path) throws RepositoryException; /** * Resolves a path into a node id. *
PathNotFoundException
Path
Item
* Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * node to be found at path. * * @param path path to resolve * @return node id referred to by path or null * if there's no node at path. * @throws RepositoryException if an error occurs */ NodeId resolveNodePath(Path path) throws RepositoryException; /** * Resolves a path into a property id. *
* Note that, for performance reasons, this method returns null * rather than throwing a PathNotFoundException if there's no * property to be found at path. * * @param path path to resolve * @return property id referred to by path or null * if there's no property at path. * @throws RepositoryException if an error occurs */ PropertyId resolvePropertyPath(Path path) throws RepositoryException; /** * Returns the path to the given item. * @param id * @return * @throws ItemNotFoundException * @throws RepositoryException */ Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item. * @param id id of item whose name should be returned * @return * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the name of the specified item, with the given parent id. If the * given item is not shareable, this is identical to {@link #getName(ItemId)}. * * @param id node id * @param parentId parent node id * @return name * @throws ItemNotFoundException * @throws RepositoryException */ Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified item which is equivalent to * getPath(id).getAncestorCount(). The depth reflects the * absolute hierarchy level. * * @param id item id * @return the depth of the specified item * @throws ItemNotFoundException if the specified id does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException; /** * Returns the depth of the specified descendant relative to the given * ancestor. If ancestorId and descendantId * denote the same item 0 is returned. If ancestorId does not * denote an ancestor -1 is returned. * * @param ancestorId ancestor id * @param descendantId descendant id * @return the relative depth; -1 if ancestorId does not * denote an ancestor of the item denoted by descendantId * (or itself). * @throws ItemNotFoundException if either of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException; /** * Determines whether the node with the specified nodeId * is an ancestor of the item denoted by the given itemId. * This is equivalent to * getPath(nodeId).isAncestorOf(getPath(itemId)). * * @param nodeId node id * @param itemId item id * @return true if the node with the specified * nodeId is an ancestor of the item denoted by the * given itemId; false otherwise * @throws ItemNotFoundException if any of the specified id's does not * denote an existing item. * @throws RepositoryException if another error occurs */ boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException; //------------------------------------------- operation with shareable nodes /** * Determines whether the node with the specified ancestor * is a share ancestor of the item denoted by the given descendant. * This is true for two nodes A, B * if either: *
getPath(id).getAncestorCount()
ancestorId
descendantId
nodeId
itemId
getPath(nodeId).isAncestorOf(getPath(itemId))
ancestor
descendant
A
B
N1
Nk
Ni
Ni+1
i
1
k-1
0
-1
HierarchyManagerImpl
elements
elements[0]
elements[next - 1]
RETURN_ANY
RETURN_NODE
RETURN_PROPERTY
* Low-level hook provided for specialized derived classes. * * @param id item id * @return item state * @throws NoSuchItemStateException if the item does not exist * @throws ItemStateException if an error occurs * @see ZombieHierarchyManager#getItemState(ItemId) */ protected ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { return provider.getItemState(id); } /** * Determines whether an item state for a given item id exists. *
* Low-level hook provided for specialized derived classes. * * @param id item id * @return true if an item state exists, otherwise * false * @see ZombieHierarchyManager#hasItemState(ItemId) */ protected boolean hasItemState(ItemId id) { return provider.hasItemState(id); } /** * Returns the parentUUID of the given item. *
parentUUID
* Low-level hook provided for specialized derived classes. * * @param state item state * @return parentUUID of the given item * @see ZombieHierarchyManager#getParentId(ItemState) */ protected NodeId getParentId(ItemState state) { return state.getParentId(); } /** * Return all parents of a node. A shareable node has possibly more than * one parent. * * @param state item state * @param useOverlayed whether to use overlayed state for shareable nodes * @return set of parent NodeIds. If state has no parent, * array has length 0. */ protected Set getParentIds(ItemState state, boolean useOverlayed) { if (state.isNode()) { // if this is a node, quickly check whether it is shareable and // whether it contains more than one parent NodeState ns = (NodeState) state; if (ns.isShareable() && useOverlayed && ns.hasOverlayedState()) { ns = (NodeState) ns.getOverlayedState(); } Set s = ns.getSharedSet(); if (s.size() > 1) { return s; } } NodeId parentId = getParentId(state); if (parentId != null) { LinkedHashSet s = new LinkedHashSet(); s.add(parentId); return s; } return Collections.emptySet(); } /** * Returns the ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. * * Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
NodeId
ChildNodeEntry
parent
uuid
* Low-level hook provided for specialized derived classes. * * @param parent node state * @param id id of child node entry * @return the ChildNodeEntry of parent with * the specified uuid or null if there's * no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, NodeId) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, NodeId id) { return parent.getChildNodeEntry(id); } /** * Returns the ChildNodeEntry of parent with the * specified name and index or null * if there's no such entry. *
name
index
* Low-level hook provided for specialized derived classes. * * @param parent node state * @param name name of child node entry * @param index index of child node entry * @return the ChildNodeEntry of parent with * the specified name and index or * null if there's no such entry. * @see ZombieHierarchyManager#getChildNodeEntry(NodeState, Name, int) */ protected ChildNodeEntry getChildNodeEntry(NodeState parent, Name name, int index) { return parent.getChildNodeEntry(name, index); } /** * Adds the path element of an item id to the path currently being built. * Recursively invoked method that may be overridden by some subclass to * either return cached responses or add response to cache. On exit, * builder contains the path of state. * * @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, CycleDetector detector) throws ItemStateException, RepositoryException { // shortcut if (state.getId().equals(rootNodeId)) { builder.addRoot(); return; } NodeId parentId = getParentId(state); if (parentId == null) { String msg = "failed to build path of " + state.getId() + ": 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, detector); if (state.isNode()) { NodeState nodeState = (NodeState) state; NodeId id = nodeState.getNodeId(); ChildNodeEntry entry = getChildNodeEntry(parent, id); if (entry == null) { String msg = "failed to build path of " + state.getId() + ": " + parent.getNodeId() + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } } else { PropertyState propState = (PropertyState) state; Name name = propState.getName(); // add to path builder.addLast(name); } } /** * Internal implementation of {@link #resolvePath(Path)} that will either * resolve to a node or a property. Should be overridden by a subclass * that can resolve an intermediate path into an ItemId. This * subclass can then invoke {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)} * with a value of next greater than 1. * * @param path path to resolve * @param typesAllowed one of RETURN_ANY, RETURN_NODE * or RETURN_PROPERTY * @return id or null * @throws RepositoryException if an error occurs */ protected ItemId resolvePath(Path path, int typesAllowed) throws RepositoryException { Path.Element[] elements = path.getElements(); ItemId id = rootNodeId; try { return resolvePath(elements, 1, id, typesAllowed); } catch (ItemStateException e) { String msg = "failed to retrieve state of intermediary node"; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Called by {@link #resolvePath(org.apache.jackrabbit.spi.Path.Element[], int, ItemId, int)}. * May be overridden by some subclass to process/cache intermediate state. * * @param id id of resolved item * @param builder path builder containing path resolved * @throws MalformedPathException if the path contained in builder * is malformed */ protected void pathResolved(ItemId id, PathBuilder builder) throws MalformedPathException { // do nothing } //-----------------------------------------------------< HierarchyManager > /** * {@inheritDoc} */ public final ItemId resolvePath(Path path) throws RepositoryException { // shortcut if (path.denotesRoot()) { return rootNodeId; } if (!path.isCanonical()) { String msg = "path is not canonical"; log.debug(msg); throw new RepositoryException(msg); } return resolvePath(path, RETURN_ANY); } /** * {@inheritDoc} */ public NodeId resolveNodePath(Path path) throws RepositoryException { return (NodeId) resolvePath(path, RETURN_NODE); } /** * {@inheritDoc} */ public PropertyId resolvePropertyPath(Path path) throws RepositoryException { return (PropertyId) resolvePath(path, RETURN_PROPERTY); } /** * {@inheritDoc} */ public Path getPath(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return PathFactoryImpl.getInstance().getRootPath(); } PathBuilder builder = new PathBuilder(); try { buildPath(builder, getItemState(id), new CycleDetector()); return builder.getPath(); } catch (NoSuchItemStateException nsise) { String msg = "failed to build path of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } catch (MalformedPathException mpe) { String msg = "failed to build path of " + id; log.debug(msg); throw new RepositoryException(msg, mpe); } } /** * {@inheritDoc} */ public Name getName(ItemId itemId) throws ItemNotFoundException, RepositoryException { if (itemId.denotesNode()) { NodeId nodeId = (NodeId) itemId; try { NodeState nodeState = (NodeState) getItemState(nodeId); NodeId parentId = getParentId(nodeState); if (parentId == null) { // this is the root or an orphaned node // FIXME return EMPTY_NAME; } return getName(nodeId, parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new ItemNotFoundException(nodeId.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + nodeId; log.debug(msg); throw new RepositoryException(msg, ise); } } else { return ((PropertyId) itemId).getName(); } } /** * {@inheritDoc} */ public Name getName(NodeId id, NodeId parentId) throws ItemNotFoundException, RepositoryException { NodeState parentState; try { parentState = (NodeState) getItemState(parentId); } catch (NoSuchItemStateException nsis) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(id.toString()); } catch (ItemStateException ise) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } ChildNodeEntry entry = getChildNodeEntry(parentState, id); if (entry == null) { String msg = "failed to resolve name of " + id; log.debug(msg); throw new ItemNotFoundException(msg); } return entry.getName(); } /** * {@inheritDoc} */ public int getDepth(ItemId id) throws ItemNotFoundException, RepositoryException { // shortcut if (id.equals(rootNodeId)) { return 0; } try { ItemState state = getItemState(id); NodeId parentId = getParentId(state); int depth = 0; while (parentId != null) { depth++; state = getItemState(parentId); parentId = getParentId(state); } return depth; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + id; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getRelativeDepth(NodeId ancestorId, ItemId descendantId) throws ItemNotFoundException, RepositoryException { if (ancestorId.equals(descendantId)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendantId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(ancestorId)) { return depth; } depth++; state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine depth of " + descendantId + " relative to " + ancestorId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isAncestor(NodeId nodeId, ItemId itemId) throws ItemNotFoundException, RepositoryException { if (nodeId.equals(itemId)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(itemId); NodeId parentId = getParentId(state); while (parentId != null) { if (parentId.equals(nodeId)) { return true; } state = getItemState(parentId); parentId = getParentId(state); } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + nodeId + " and " + itemId; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public boolean isShareAncestor(NodeId ancestor, NodeId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { // can't be ancestor of self return false; } try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, false); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return true; } Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { grandparentIds.addAll(getParentIds(getItemState(parentId), false)); } parentIds = grandparentIds; } // not an ancestor return false; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new RepositoryException(msg, ise); } } /** * {@inheritDoc} */ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant) throws ItemNotFoundException, RepositoryException { if (ancestor.equals(descendant)) { return 0; } int depth = 1; try { ItemState state = getItemState(descendant); Set parentIds = getParentIds(state, true); while (parentIds.size() > 0) { if (parentIds.contains(ancestor)) { return depth; } depth++; Set grandparentIds = new LinkedHashSet(); for (NodeId parentId : parentIds) { state = getItemState(parentId); grandparentIds.addAll(getParentIds(state, true)); } parentIds = grandparentIds; } // not an ancestor return -1; } catch (NoSuchItemStateException nsise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); throw new ItemNotFoundException(msg, nsise); } catch (ItemStateException ise) { String msg = "failed to determine degree of relationship of " + ancestor + " and " + descendant; log.debug(msg); 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 ids; boolean checkCycle(ItemId id) throws InvalidItemStateException { if (count++ >= 15) { if (ids == null) { ids = new HashSet(); } else { return !ids.add(id); } } return false; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object referenced by different ItemImpl instances that * all represent the same item, i.e. items having the same ItemId. */ public abstract class ItemData { /** Associated item id */ private final ItemId id; /** Associated item state */ private ItemState state; /** Associated item definition */ private ItemDefinition definition; /** Status */ private int status; /** The item manager */ private ItemManager itemMgr; /** * Create a new instance of this class. * * @param state item state * @param itemMgr item manager */ protected ItemData(ItemState state, ItemManager itemMgr) { this.id = state.getId(); this.state = state; this.itemMgr = itemMgr; this.status = ItemImpl.STATUS_NORMAL; } /** * Create a new instance of this class. * * @param id item id */ protected ItemData(ItemId id) { this.id = id; this.status = ItemImpl.STATUS_NORMAL; } /** * Return the associated item state. * * @return item state */ public ItemState getState() { return state; } /** * Set the associated item state. * * @param state item state */ protected void setState(ItemState state) { this.state = state; } /** * Return the associated item definition. * * @return item definition * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { if (definition == null && itemMgr != null) { if (isNode()) { definition = itemMgr.getDefinition((NodeState) state); } else { definition = itemMgr.getDefinition((PropertyState) state); } } return definition; } /** * Set the associated item definition. * * @param definition item definition */ protected void setDefinition(ItemDefinition definition) { this.definition = definition; } /** * Return the status. * * @return status */ public int getStatus() { return status; } /** * Set the status. * * @param status */ protected void setStatus(int status) { this.status = status; } /** * Return a flag indicating whether item is a node. * * @return true if this item is a node; * false otherwise. */ public boolean isNode() { return false; } /** * Return the id associated with this item. * * @return item id */ public ItemId getId() { return id; } /** * Return the parent id of this item. * * @return parent id */ public NodeId getParentId() { return getState().getParentId(); } /** * {@inheritDoc} */ public String toString() { return getId().toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.value.ValueHelper; /** * ItemImpl implements the Item interface. */ public abstract class ItemImpl implements Item { protected static final int STATUS_NORMAL = 0; protected static final int STATUS_MODIFIED = 1; protected static final int STATUS_DESTROYED = 2; protected static final int STATUS_INVALIDATED = 3; protected final ItemId id; /** * The component context of the session to which this item is associated. */ protected final SessionContext sessionContext; /** * Item data associated with this item. */ protected final ItemData data; /** * ItemManager that created this Item */ protected final ItemManager itemMgr; /** * SessionItemStateManager associated with this Item */ protected final SessionItemStateManager stateMgr; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Item * @param sessionContext the component context of the associated session * @param data ItemData of this Item */ ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) { this.sessionContext = sessionContext; this.stateMgr = sessionContext.getItemStateManager(); this.id = data.getId(); this.itemMgr = itemMgr; this.data = data; } protected T perform(final SessionOperation operation) throws RepositoryException { itemSanityCheck(); return sessionContext.getSessionState().perform(operation); } /** * Performs a sanity check on this item and the associated session. * * @throws RepositoryException if this item has been rendered invalid for some reason */ protected void sanityCheck() throws RepositoryException { // check session status sessionContext.getSessionState().checkAlive(); // check status of this item for read operation itemSanityCheck(); } /** * Checks the status of this item. * * @throws RepositoryException if this item no longer exists */ protected void itemSanityCheck() throws RepositoryException { // check status of this item for read operation final int status = data.getStatus(); if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) { throw new InvalidItemStateException( "Item does not exist anymore: " + id); } } protected boolean isTransient() { return getItemState().isTransient(); } protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException; protected abstract void makePersistent() throws RepositoryException; /** * Marks this instance as 'removed' and notifies its listeners. * The resulting state is either 'temporarily invalidated' or * 'permanently invalidated', depending on the initial state. * * @throws RepositoryException if an error occurs */ protected void setRemoved() throws RepositoryException { final int status = data.getStatus(); if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) { // this instance is already 'invalid', get outta here return; } ItemState transientState = getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW) { // this is a 'new' item, simply dispose the transient state // (it is no longer used); this will indirectly (through // stateDiscarded listener method) invalidate this instance permanently stateMgr.disposeTransientItemState(transientState); } else { // this is an 'existing' item (i.e. it is backed by persistent // state), mark it as 'removed' transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED); // transfer the transient state to the attic stateMgr.moveTransientItemStateToAttic(transientState); // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); } } /** * Returns the item-state associated with this Item. * * @return state associated with this Item */ ItemState getItemState() { return data.getState(); } /** * Return the id of this Item. * * @return the id of this Item */ public ItemId getId() { return id; } /** * Returns the primary path to this Item. * * @return the primary path to this Item */ public Path getPrimaryPath() throws RepositoryException { return sessionContext.getHierarchyManager().getPath(id); } /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * * @return JCR path or some fallback value */ public String safeGetJCRPath() { return itemMgr.safeGetJCRPath(id); } /** * Same as {@link Item#getName()} except that * this method returns a Name instead of a * String. * * @return the name of this item as Name * @throws RepositoryException if an error occurs. */ public abstract Name getQName() throws RepositoryException; /** * Utility method that converts the given string into a qualified JCR name. * * @param name name string * @return qualified name * @throws RepositoryException if the given name is invalid */ protected Name getQName(String name) throws RepositoryException { return sessionContext.getQName(name); } /** * Utility method that returns the value factory of this session. * * @return value factory * @throws RepositoryException if the value factory is not available */ protected ValueFactory getValueFactory() throws RepositoryException { return getSession().getValueFactory(); } /** * Utility method that converts the given strings into JCR values of the * given type * * @param values value strings * @param type value type * @return JCR values * @throws RepositoryException if the values can not be converted */ protected Value[] getValues(String[] values, int type) throws RepositoryException { if (values != null) { return ValueHelper.convert(values, type, getValueFactory()); } else { return null; } } /** * Utility method that returns the type of the first of the given values, * or {@link PropertyType#UNDEFINED} when given no values. * * @param values given values, or null * @return value type, or {@link PropertyType#UNDEFINED} */ protected int getType(Value[] values) { if (values != null) { for (Value value : values) { if (value != null) { return value.getType(); } } } return PropertyType.UNDEFINED; } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ public abstract void accept(ItemVisitor visitor) throws RepositoryException; /** * {@inheritDoc} */ public abstract boolean isNode(); /** * {@inheritDoc} */ public abstract String getName() throws RepositoryException; /** * {@inheritDoc} */ public abstract Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException; /** * {@inheritDoc} */ public boolean isNew() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() == null; } /** * checks if this item is new. running outside of transactions, this * is the same as {@link #isNew()} but within a transaction an item can * be saved but not yet persisted. */ protected boolean isTransactionalNew() { final ItemState state = getItemState(); return state.getStatus() == ItemState.STATUS_NEW; } /** * {@inheritDoc} */ public boolean isModified() { final ItemState state = getItemState(); return state.isTransient() && state.getOverlayedState() != null; } /** * {@inheritDoc} */ public void remove() throws RepositoryException { perform(new ItemRemoveOperation(this, true)); } /** * {@inheritDoc} */ public void save() throws RepositoryException { perform(new ItemSaveOperation(getItemState())); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { perform(new ItemRefreshOperation(getItemState(), keepChanges)); } /** * {@inheritDoc} */ public Item getAncestor(final int degree) throws RepositoryException { return perform(new SessionOperation() { public Item perform(SessionContext context) throws RepositoryException { if (degree == 0) { return context.getItemManager().getRootNode(); } try { // Path.getAncestor requires relative degree, i.e. we need // to convert absolute to relative ancestor degree Path path = getPrimaryPath(); int relDegree = path.getAncestorCount() - degree; if (relDegree < 0) { throw new ItemNotFoundException(); } else if (relDegree == 0) { return ItemImpl.this; // shortcut } Path ancestorPath = path.getAncestor(relDegree); return context.getItemManager().getNode(ancestorPath); } catch (PathNotFoundException e) { throw new ItemNotFoundException("Ancestor not found", e); } } public String toString() { return "item.getAncestor(" + degree + ")"; } }); } /** * {@inheritDoc} */ public String getPath() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { return context.getJCRPath(getPrimaryPath()); } public String toString() { return "item.getPath()"; } }); } /** * {@inheritDoc} */ public int getDepth() throws RepositoryException { return perform(new SessionOperation() { public Integer perform(SessionContext context) throws RepositoryException { ItemState state = getItemState(); if (state.getParentId() == null) { return 0; // shortcut } else { return context.getHierarchyManager().getDepth(id); } } public String toString() { return "item.getDepth()"; } }); } /** * Returns the session associated with this item. * * Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). * * Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. * * The ItemManager's responsibilities are: * * providing access to Item instances by ItemId * whereas Node and Item are only providing relative access. * returning the instance of an existing Node or Property, * given its absolute path. * creating the per-session instance of a Node * or Property that doesn't exist yet and needs to be created first. * guaranteeing that there aren't multiple instances representing the same * Node or Property associated with the same * Session instance. * maintaining a cache of the item instances it created. * respecting access rights of associated Session in all methods. * * * If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
builder
ItemId
next
ItemImpl
ItemManager
SessionItemStateManager
{@link Item#getName()}
Name
String
* Since Jackrabbit 1.4 it is safe to use this method regardless * of item state. * * @see Issue JCR-911 * @return current session */ public Session getSession() { return sessionContext.getSessionImpl(); } /** * {@inheritDoc} */ public boolean isSame(Item otherItem) throws RepositoryException { // check state of this instance sanityCheck(); if (this == otherItem) { return true; } if (otherItem instanceof ItemImpl) { ItemImpl other = (ItemImpl) otherItem; return id.equals(other.id) && getSession().getWorkspace().getName().equals( other.getSession().getWorkspace().getName()); } return false; } //--------------------------------------------------------------< Object > /** * Returns the({@link #safeGetJCRPath() safe}) path of this item for use * in diagnostic output. * * @return "/path/to/item" */ public String toString() { return safeGetJCRPath(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.id.ItemId; /** * The ItemLifeCycleListener interface allows an implementing * object to be informed about changes on an Item instance. */ public interface ItemLifeCycleListener { /** * Called when an ItemImpl instance has been created. * * @param item the instance which has been created */ void itemCreated(ItemImpl item); /** * Called when an ItemImpl instance has been invalidated * (i.e. it has been temporarily rendered 'invalid'). *
ItemLifeCycleListener
* Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on an 'invalidated' item. * * @param id the id of the instance that has been discarded * @param item the instance which has been discarded */ void itemInvalidated(ItemId id, ItemImpl item); /** * Called when an ItemImpl instance has been destroyed * (i.e. it has been permanently rendered 'invalid'). *
{@link javax.jcr.Item}
{@link javax.jcr.Node}
{@link javax.jcr.Property}
InvalidItemStateException
* Note that most {@link javax.jcr.Item}, * {@link javax.jcr.Node} and {@link javax.jcr.Property} * methods will throw an InvalidItemStateException when called * on a 'destroyed' item. * * @param id the id of the instance that has been destroyed * @param item the instance which has been destroyed */ void itemDestroyed(ItemId id, ItemImpl item); } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Iterator; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; import org.apache.commons.collections4.map.ReferenceMap; 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.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateListener; 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.state.SessionItemStateManager; import org.apache.jackrabbit.core.version.VersionHistoryImpl; import org.apache.jackrabbit.core.version.VersionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * There's one ItemManager instance per Session * instance. It is the factory for Node and Property * instances. *
Session
Node
Property
* The ItemManager's responsibilities are: *
* If the parent Session is an XASession, there is * one ItemManager instance per started global transaction. */ public class ItemManager implements ItemStateListener { private static Logger log = LoggerFactory.getLogger(ItemManager.class); private final org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl rootNodeDef; /** * Component context of the associated session. */ protected final SessionContext sessionContext; protected final SessionImpl session; private final SessionItemStateManager sism; private final HierarchyManager hierMgr; /** * A cache for item instances created by this ItemManager */ private final Map itemCache; /** * Shareable node cache. */ private final ShareableNodesCache shareableNodesCache; /** * Creates a new per-session instance ItemManager instance. * * @param sessionContext component context of the associated session */ protected ItemManager(SessionContext sessionContext) { this.sism = sessionContext.getItemStateManager(); this.hierMgr = sessionContext.getHierarchyManager(); this.sessionContext = sessionContext; this.session = sessionContext.getSessionImpl(); this.rootNodeDef = sessionContext.getNodeTypeManager().getRootNodeDefinition(); // setup item cache with weak references to items itemCache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); // setup shareable nodes cache shareableNodesCache = new ShareableNodesCache(); } /** * Checks that this session is alive. * * @throws RepositoryException if the session has been closed */ private void sanityCheck() throws RepositoryException { sessionContext.getSessionState().checkAlive(); } /** * Disposes this ItemManager and frees resources. */ void dispose() { synchronized (itemCache) { itemCache.clear(); } shareableNodesCache.clear(); } NodeDefinitionImpl getDefinition(NodeState state) throws RepositoryException { if (state.getId().equals(sessionContext.getRootNodeId())) { // special handling required for root node return rootNodeDef; } NodeId parentId = state.getParentId(); if (parentId == null) { // removed state has parentId set to null // get from overlayed state ItemState overlaid = state.getOverlayedState(); if (overlaid != null) { parentId = overlaid.getParentId(); } else { throw new InvalidItemStateException( "Could not find parent of node " + state.getNodeId()); } } NodeState parentState = null; try { // access the parent state circumventing permission check, since // read permission on the parent isn't required in order to retrieve // a node's definition. see also JCR-2418 ItemData parentData = getItemData(parentId, null, false); parentState = (NodeState) parentData.getState(); if (state.getParentId() == null) { // indicates state has been removed, must use // overlayed state of parent, otherwise child node entry // cannot be found. unless the parentState is new, which // means it was recreated in place of a removed node // that used to be the actual parent if (parentState.getStatus() == ItemState.STATUS_NEW) { // force getting parent from attic parentState = null; } else { parentState = (NodeState) parentState.getOverlayedState(); } } } catch (ItemNotFoundException e) { // parent probably removed, get it from attic. see below } if (parentState == null) { try { // use overlayed state if available parentState = (NodeState) sism.getAttic().getItemState( parentId).getOverlayedState(); } catch (ItemStateException ex) { throw new RepositoryException(ex); } } // get child node entry ChildNodeEntry cne = parentState.getChildNodeEntry(state.getNodeId()); if (cne == null) { throw new InvalidItemStateException( "Could not find child " + state.getNodeId() + " of node " + parentState.getNodeId()); } NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); try { EffectiveNodeType ent = ntReg.getEffectiveNodeType( parentState.getNodeTypeName(), parentState.getMixinTypeNames()); QNodeDefinition def; try { def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); } catch (ConstraintViolationException e) { // fallback to child node definition of a nt:unstructured ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicableChildNodeDef( cne.getName(), state.getNodeTypeName(), ntReg); log.warn("Fallback to nt:unstructured due to unknown child " + "node definition for type '" + state.getNodeTypeName() + "'"); } return sessionContext.getNodeTypeManager().getNodeDefinition(def); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } PropertyDefinitionImpl getDefinition(PropertyState state) throws RepositoryException { // this is a bit ugly // there might be cases where otherwise protected items turn into // non-protected items because a mixin has been removed from the parent // node state. // see also: JCR-2408 if (state.getStatus() == ItemState.STATUS_EXISTING_REMOVED && state.getName().equals(NameConstants.JCR_UUID)) { NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); QPropertyDefinition def = ntReg.getEffectiveNodeType( NameConstants.MIX_REFERENCEABLE).getApplicablePropertyDef( state.getName(), state.getType()); return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } try { // retrieve parent in 2 steps in order to avoid the check for // read permissions on the parent which isn't required in order // to read the property's definition. see also JCR-2418. ItemData parentData = getItemData(state.getParentId(), null, false); NodeImpl parent = (NodeImpl) createItemInstance(parentData); return parent.getApplicablePropertyDefinition( state.getName(), state.getType(), state.isMultiValued(), true); } catch (ItemNotFoundException e) { // parent probably removed, get it from attic } try { NodeState parent = (NodeState) sism.getAttic().getItemState( state.getParentId()).getOverlayedState(); NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); EffectiveNodeType ent = ntReg.getEffectiveNodeType( parent.getNodeTypeName(), parent.getMixinTypeNames()); QPropertyDefinition def; try { def = ent.getApplicablePropertyDef( state.getName(), state.getType(), state.isMultiValued()); } catch (ConstraintViolationException e) { ent = ntReg.getEffectiveNodeType(NameConstants.NT_UNSTRUCTURED); def = ent.getApplicablePropertyDef(state.getName(), state.getType(), state.isMultiValued()); log.warn("Fallback to nt:unstructured due to unknown property " + "definition for '" + state.getName() + "'"); } return sessionContext.getNodeTypeManager().getPropertyDefinition(def); } catch (ItemStateException e) { throw new RepositoryException(e); } catch (NodeTypeConflictException e) { throw new RepositoryException(e); } } /** * Common implementation for all variants of item/node/propertyExists * with both itemId or path param. * * @param itemId The id of the item to test. * @param path Path of the item to check if known or null. In * the latter case the test for access permission is executed using the * itemId. * @return true if the item with the given itemId exists AND * can be read by this session. */ private boolean itemExists(ItemId itemId, Path path) { try { sanityCheck(); // shortcut: check if state exists for the given item if (!sism.hasItemState(itemId)) { return false; } getItemData(itemId, path, true); return true; } catch (RepositoryException re) { return false; } } /** * Common implementation for all variants of getItem/getNode/getProperty * with both itemId or path parameter. * * @param itemId * @param path Path of the item to retrieve or null. In * the latter case the test for access permission is executed using the * itemId. * @param permissionCheck * @return The item identified by the given itemId. * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ private ItemImpl getItem(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(itemId, path, permissionCheck); return createItemInstance(data); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If the item exists but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @return state state of said item * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ private ItemData getItemData(ItemId itemId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItemData(itemId, null, true); } /** * Retrieves the data of the item with given id. If the * specified item doesn't exist an ItemNotFoundException will * be thrown. * If permissionCheck is true and the item exists * but the current session is not granted read access an * AccessDeniedException will be thrown. * * @param itemId id of item to be retrieved * @param path The path of the item to retrieve the data for or * null. In the latter case the id (instead of the path) is * used to test if READ permission is granted. * @param permissionCheck * @return the ItemData for the item identified by the given itemId. * @throws ItemNotFoundException if no item with given id exists * @throws AccessDeniedException if the current session is not allowed to * read the said item * @throws RepositoryException if another error occurs */ ItemData getItemData(ItemId itemId, Path path, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { ItemData data = retrieveItem(itemId); if (data == null) { // not yet in cache, need to create instance: // - retrieve item state // - create instance of item data // NOTE: permission check & caching within createItemData ItemState state; try { state = sism.getItemState(itemId); } catch (NoSuchItemStateException nsise) { throw new ItemNotFoundException(itemId.toString(), nsise); } catch (ItemStateException ise) { String msg = "failed to retrieve item state of item " + itemId; log.error(msg, ise); throw new RepositoryException(msg, ise); } // create item data including: perm check and caching. data = createItemData(state, path, permissionCheck); } else { // already cached: if 'permissionCheck' is true, make sure read // permission is granted. if (permissionCheck && !canRead(data, path)) { // item exists but read-perm has been revoked in the mean time. // -> remove from cache evictItems(itemId); throw new AccessDeniedException("cannot read item " + data.getId()); } } return data; } /** * @param data * @param path Path to be used for the permission check or null * in which case the itemId present with the specified data is used. * @return true if the item with the given data can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData data, Path path) throws RepositoryException { // JCR-1601: cached item may just have been invalidated ItemState state = data.getState(); if (state == null) { throw new InvalidItemStateException(data.getId() + ": the item does not exist anymore"); } if (state.getStatus() == ItemState.STATUS_NEW) { if (!data.getDefinition().isProtected()) { /* NEW items can always be read as long they have been added through the API and NOT by the system (i.e. protected items). */ return true; } else { /* NEW protected (system) item: need use the path to evaluate the effective permissions. */ return (path == null) ? sessionContext.getAccessManager().isGranted(data.getId(), AccessManager.READ) : sessionContext.getAccessManager().isGranted(path, Permission.READ); } } else { /* item is not NEW -> save to call acMgr.canRead(Path,ItemId) */ return sessionContext.getAccessManager().canRead(path, data.getId()); } } /** * @param parent The item data of the parent node. * @param childId * @return true if the item with the given childId can be read; * false otherwise. * @throws RepositoryException */ private boolean canRead(ItemData parent, ItemId childId) throws RepositoryException { if (parent.getStatus() == ItemState.STATUS_EXISTING) { // child item is for sure not NEW (because then the parent was modified). // safe to use AccessManager#canRead(Path, ItemId). return sessionContext.getAccessManager().canRead(null, childId); } else { // child could be NEW -> don't use AccessManager#canRead(Path, ItemId) return sessionContext.getAccessManager().isGranted(childId, AccessManager.READ); } } //--------------------------------------------------< item access methods > /** * Checks whether an item exists at the specified path. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #nodeExists(Path)} and * {@link #propertyExists(Path)} should be used instead. * * @param path path to the item to be checked * @return true if the specified item exists */ @Deprecated public boolean itemExists(Path path) { try { sanityCheck(); ItemId id = hierMgr.resolvePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a node exists at the specified path. * * @param path path to the node to be checked * @return true if a node exists at the specified path */ public boolean nodeExists(Path path) { try { sanityCheck(); NodeId id = hierMgr.resolveNodePath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks whether a property exists at the specified path. * * @param path path to the property to be checked * @return true if a property exists at the specified path */ public boolean propertyExists(Path path) { try { sanityCheck(); PropertyId id = hierMgr.resolvePropertyPath(path); return (id != null) && itemExists(id, path); } catch (RepositoryException re) { return false; } } /** * Checks if the item with the given id exists. * * @param id id of the item to be checked * @return true if the specified item exists */ public boolean itemExists(ItemId id) { return itemExists(id, null); } /** * @return * @throws RepositoryException */ NodeImpl getRootNode() throws RepositoryException { return (NodeImpl) getItem(sessionContext.getRootNodeId()); } /** * Returns the node at the specified absolute path in the workspace. * If no such node exists, then it returns the property at the specified path. * If no such property exists a PathNotFoundException is thrown. * * @deprecated As of JSR 283, a Path doesn't anymore uniquely * identify an Item, therefore {@link #getNode(Path)} and * {@link #getProperty(Path)} should be used instead. * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ @Deprecated public ItemImpl getItem(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { ItemId id = hierMgr.resolvePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { ItemImpl item = getItem(id, path, true); // Test, if this item is a shareable node. if (item.isNode() && ((NodeImpl) item).isShareable()) { return getNode(path); } return item; } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public NodeImpl getNode(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { NodeId id = hierMgr.resolveNodePath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } NodeId parentId = null; if (!path.denotesRoot()) { parentId = hierMgr.resolveNodePath(path.getAncestor(1)); } try { if (parentId == null) { return (NodeImpl) getItem(id, path, true); } // if the node is shareable, it now returns the node with the right // parent return getNode(id, parentId); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param path * @return * @throws PathNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ public PropertyImpl getProperty(Path path) throws PathNotFoundException, AccessDeniedException, RepositoryException { PropertyId id = hierMgr.resolvePropertyPath(path); if (id == null) { throw new PathNotFoundException(safeGetJCRPath(path)); } try { return (PropertyImpl) getItem(id, path, true); } catch (ItemNotFoundException infe) { throw new PathNotFoundException(safeGetJCRPath(path)); } } /** * @param id * @return * @throws RepositoryException */ public synchronized ItemImpl getItem(ItemId id) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, true); } /** * @param id * @return * @throws RepositoryException */ synchronized ItemImpl getItem(ItemId id, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getItem(id, null, permissionCheck); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @return node * @throws RepositoryException if an error occurs */ public synchronized NodeImpl getNode(NodeId id, NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { return getNode(id, parentId, true); } /** * Returns a node with a given id and parent id. If the indicated node is * shareable, there might be multiple nodes associated with the same id, * but there'is only one node with the given parent id. * * @param id node id * @param parentId parent node id * @param permissionCheck Flag indicating if read permission must be check * upon retrieving the node. * @return node * @throws RepositoryException if an error occurs */ synchronized NodeImpl getNode(NodeId id, NodeId parentId, boolean permissionCheck) throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (parentId == null) { return (NodeImpl) getItem(id); } 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 if (!data.getNodeState().containsShare(parentId)) { String msg = "Node with id '" + id + "' does not have shared parent with id: " + parentId; throw new ItemNotFoundException(msg); } // TODO: ev. need to check if read perm. is granted. data = new NodeDataRef(data, parentId); cacheItem(data); } return createNodeInstance(data); } /** * Create an item instance from an item state. This method creates a * new ItemData instance without looking at the cache nor * testing if the item can be read and returns a new item instance. * * @param state item state * @return item instance * @throws RepositoryException if an error occurs */ synchronized ItemImpl createItemInstance(ItemState state) throws RepositoryException { ItemData data = createItemData(state, null, false); return createItemInstance(data); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } NodeState state = (NodeState) data.getState(); for (ChildNodeEntry entry : state.getChildNodeEntries()) { // make sure any of the properties can be read. if (canRead(data, entry.getId())) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized NodeIterator getChildNodes(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child nodes of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getChildNodeEntries().iterator(); while (iter.hasNext()) { ChildNodeEntry entry = iter.next(); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(entry.getId()); } return new LazyItemIterator(sessionContext, childIds, parentId); } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized boolean hasChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); // make sure any of the properties can be read. if (canRead(data, new PropertyId(parentId, propName))) { return true; } } return false; } /** * @param parentId * @return * @throws ItemNotFoundException * @throws AccessDeniedException * @throws RepositoryException */ synchronized PropertyIterator getChildProperties(NodeId parentId) throws ItemNotFoundException, AccessDeniedException, RepositoryException { sanityCheck(); ItemData data = getItemData(parentId); if (!data.isNode()) { String msg = "can't list child properties of property " + parentId; log.debug(msg); throw new RepositoryException(msg); } ArrayList childIds = new ArrayList(); Iterator iter = ((NodeState) data.getState()).getPropertyNames().iterator(); while (iter.hasNext()) { Name propName = iter.next(); PropertyId id = new PropertyId(parentId, propName); // delay check for read-access until item is being built // thus avoid duplicate check childIds.add(id); } return new LazyItemIterator(sessionContext, childIds); } //-------------------------------------------------< item factory methods > /** * Builds the ItemData for the specified state. * If permissionCheck is true, the access manager * is used to determine if reading that item would be granted. If this is * not the case an AccessDeniedException is thrown. * Before returning the created ItemData it is put into the * cache. In order to benefit from the cache * {@link #getItemData(ItemId, Path, boolean)} should be called. * * @param state * @return * @throws RepositoryException */ private ItemData createItemData(ItemState state, Path path, boolean permissionCheck) throws RepositoryException { ItemData data; if (state.isNode()) { NodeState nodeState = (NodeState) state; data = new NodeData(nodeState, this); } else { PropertyState propertyState = (PropertyState) state; data = new PropertyData(propertyState, this); } // make sure read-perm. is granted before returning the data. if (permissionCheck && !canRead(data, path)) { throw new AccessDeniedException("cannot read item " + state.getId()); } // before returning the data: put them into the cache. cacheItem(data); return data; } private ItemImpl createItemInstance(ItemData data) { if (data.isNode()) { return createNodeInstance((AbstractNodeData) data); } else { return createPropertyInstance((PropertyData) data); } } private NodeImpl createNodeInstance(AbstractNodeData data) { // check special nodes final NodeState state = data.getNodeState(); if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) { return new VersionImpl(this, sessionContext, data); } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) { return new VersionHistoryImpl(this, sessionContext, data); } else { // create node object return new NodeImpl(this, sessionContext, data); } } private PropertyImpl createPropertyInstance(PropertyData data) { // check special nodes return new PropertyImpl(this, sessionContext, data); } //---------------------------------------------------< item cache methods > /** * Returns an item reference from the cache. * * @param id id of the item that should be retrieved. * @return the item reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private ItemData retrieveItem(ItemId id) { synchronized (itemCache) { ItemData data = itemCache.get(id); if (data == null && id.denotesNode()) { data = shareableNodesCache.retrieveFirst((NodeId) id); } return data; } } /** * Return a node from the cache. * * @param id id of the node that should be retrieved. * @param parentId parent id of the node that should be retrieved * @return reference stored in the corresponding cache entry * or null if there's no corresponding cache entry. */ private AbstractNodeData retrieveItem(NodeId id, NodeId parentId) { synchronized (itemCache) { AbstractNodeData data = shareableNodesCache.retrieve(id, parentId); if (data == null) { data = (AbstractNodeData) itemCache.get(id); } return data; } } /** * Puts the reference of an item in the cache with * the item's path as the key. * * @param data the item data to cache */ private void cacheItem(ItemData data) { synchronized (itemCache) { if (data.isNode()) { AbstractNodeData nd = (AbstractNodeData) data; if (nd.getPrimaryParentId() != null) { shareableNodesCache.cache(nd); return; } } ItemId id = data.getId(); if (itemCache.containsKey(id)) { log.debug("overwriting cached item " + id); } if (log.isDebugEnabled()) { log.debug("caching item " + id); } itemCache.put(id, data); } } /** * Removes all cache entries with the given item id. If the item is * shareable, there might be more than one cache entry for this item. * * @param id id of the items to remove from the cache */ private void evictItems(ItemId id) { if (log.isDebugEnabled()) { log.debug("removing items " + id + " from cache"); } synchronized (itemCache) { itemCache.remove(id); if (id.denotesNode()) { shareableNodesCache.evictAll((NodeId) id); } } } /** * Removes a cache entry for a specific item. * * @param data The item data to remove from the cache */ private void evictItem(ItemData data) { if (log.isDebugEnabled()) { log.debug("removing item " + data.getId() + " from cache"); } synchronized (itemCache) { if (data.isNode()) { shareableNodesCache.evict((AbstractNodeData) data); } ItemData cached = itemCache.get(data.getId()); if (cached == data) { itemCache.remove(data.getId()); } } } //-------------------------------------------------< misc. helper methods > /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ String safeGetJCRPath(Path path) { try { return session.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert " + path.toString() + " to JCR path."); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use in * error messages etc. * * @param id path to convert * @return JCR path */ String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath(hierMgr.getPath(id)); } catch (RepositoryException re) { log.error(id + ": failed to determine path to"); // return string representation if id as a fallback return id.toString(); } } //------------------------------------------------< ItemLifeCycleListener > /** * {@inheritDoc} */ public void itemInvalidated(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("invalidated item " + id); } evictItem(data); } /** * {@inheritDoc} */ public void itemDestroyed(ItemId id, ItemData data) { if (log.isDebugEnabled()) { log.debug("destroyed item " + id); } synchronized (itemCache) { // remove instance from cache evictItems(id); } } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("ItemManager (" + super.toString() + ")\n"); builder.append("Items in cache:\n"); synchronized (itemCache) { for (ItemId id : itemCache.keySet()) { ItemData item = itemCache.get(id); if (item.isNode()) { builder.append("Node: "); } else { builder.append("Property: "); } if (item.getState().isTransient()) { builder.append("transient "); } else { builder.append(" "); } builder.append(id + "\t" + safeGetJCRPath(id) + " (" + item + ")\n"); } } return builder.toString(); } //----------------------------------------------------< ItemStateListener > /** * {@inheritDoc} */ public void stateCreated(ItemState created) { ItemData data = retrieveItem(created.getId()); if (data != null) { data.setStatus(ItemImpl.STATUS_NORMAL); } } /** * {@inheritDoc} */ public void stateModified(ItemState modified) { ItemData data = retrieveItem(modified.getId()); if (data != null && data.getState() == modified) { data.setStatus(ItemImpl.STATUS_MODIFIED); /* if (modified.isNode()) { NodeState state = (NodeState) modified; if (state.isShareable()) { //evictItem(modified.getId()); NodeData nodeData = (NodeData) data; NodeData shareSibling = new NodeData(nodeData, state.getParentId()); shareableNodesCache.cache(shareSibling); } } */ } } /** * {@inheritDoc} */ public void stateDestroyed(ItemState destroyed) { ItemData data = retrieveItem(destroyed.getId()); if (data != null && data.getState() == destroyed) { itemDestroyed(destroyed.getId(), data); data.setStatus(ItemImpl.STATUS_DESTROYED); } } /** * {@inheritDoc} */ public void stateDiscarded(ItemState discarded) { ItemData data = retrieveItem(discarded.getId()); if (data != null && data.getState() == discarded) { if (discarded.isTransient()) { switch (discarded.getStatus()) { /** * persistent item that has been transiently removed */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state // and resurrect this item instance if necessary SessionItemStateManager stateMgr = sessionContext.getItemStateManager(); stateMgr.disconnectTransientItemState(discarded); data.setState(persistentState); return; /** * persistent item that has been transiently modified or * removed and the underlying persistent state has been * externally destroyed since the transient * modification/removal. */ case ItemState.STATUS_STALE_DESTROYED: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; /** * new item that has been transiently added */ case ItemState.STATUS_NEW: /** * first notify the listeners that this instance has been * permanently invalidated */ itemDestroyed(discarded.getId(), data); // now set state of this instance to 'destroyed' // finally dispose state data.setStatus(ItemImpl.STATUS_DESTROYED); data.setState(null); return; } } /** * first notify the listeners that this instance has been * invalidated */ itemInvalidated(discarded.getId(), data); // now render this instance 'invalid' data.setStatus(ItemImpl.STATUS_INVALIDATED); } } /** * Cache of shareable nodes. For performance reasons, methods are not * synchronized and thread-safety must be guaranteed by caller. */ static class ShareableNodesCache { /** * This cache is based on a reference map, that maps an item id to a map, * which again maps a (hard-ref) parent id to a (weak-ref) shareable node. */ private final ReferenceMap> cache; /** * Create a new instance of this class. */ public ShareableNodesCache() { cache = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD); } /** * Clear cache. * * @see ReferenceMap#clear() */ public void clear() { cache.clear(); } /** * Return the first available node that maps to the given id. * * @param id node id * @return node or null */ public AbstractNodeData retrieveFirst(NodeId id) { ReferenceMap map = cache.get(id); if (map != null) { Iterator iter = map.values().iterator(); try { while (iter.hasNext()) { AbstractNodeData data = iter.next(); if (data != null) { return data; } } } finally { iter = null; } } return null; } /** * Return the node with the given id and parent id. * * @param id node id * @param parentId parent id * @return node or null */ public AbstractNodeData retrieve(NodeId id, NodeId parentId) { ReferenceMap map = cache.get(id); if (map != null) { return map.get(parentId); } return null; } /** * Cache some node. * * @param data data to cache */ public void cache(AbstractNodeData data) { NodeId id = data.getNodeState().getNodeId(); ReferenceMap map = cache.get(id); if (map == null) { map = new ReferenceMap<>(ReferenceStrength.HARD, ReferenceStrength.WEAK); cache.put(id, map); } Object old = map.put(data.getPrimaryParentId(), data); if (old != null) { log.debug("overwriting cached item: " + old); } } /** * Evict some node from the cache. * * @param data data to evict */ public void evict(AbstractNodeData data) { ReferenceMap map = cache.get(data.getId()); if (map != null) { map.remove(data.getPrimaryParentId()); } } /** * Evict all nodes with a given node id from the cache. * * @param id node id to evict */ public synchronized void evictAll(NodeId id) { cache.remove(id); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemRefreshOperation implements SessionOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemRefreshOperation.class); private final ItemState state; private final boolean keepChanges; public ItemRefreshOperation(ItemState state, boolean keepChanges) { this.state = state; this.keepChanges = keepChanges; } public Object perform(SessionContext context) throws RepositoryException { if (keepChanges) { // FIXME When keepChanges is true, should reset Item#status field // to STATUS_NORMAL of all descendant non-transient instances; // maybe also have to reset stale ItemState instances return this; } SessionItemStateManager stateMgr = context.getItemStateManager(); // Optimisation for the root node if (state.getParentId() == null) { stateMgr.disposeAllTransientItemStates(); return this; } // list of transient items that should be discarded List transientStates = new ArrayList(); // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); break; case ItemState.STATUS_EXISTING_MODIFIED: if (!state.getParentId().equals( state.getOverlayedState().getParentId())) { throw new RepositoryException( "Cannot refresh a moved item," + " try refreshing the parent: " + this); } transientStates.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot refresh a new item: " + this); default: // log and ignore log.warn("Unexpected item state status {} of {}", state.getStatus(), this); break; } } if (state.isNode()) { // build list of 'new', 'modified' or 'stale' descendants for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add new or modified state to the list transientStates.add(transientState); break; default: // log and ignore log.debug("unexpected state status ({})", transientState.getStatus()); break; } } } // process list of 'new', 'modified' or 'stale' transient states for (ItemState transientState : transientStates) { // dispose the transient state, it is no longer used; // this will indirectly (through stateDiscarded listener method) // either restore or permanently invalidate the wrapping Item instances stateMgr.disposeTransientItemState(transientState); } if (state.isNode()) { // discard all transient descendants in the attic (i.e. those marked // as 'removed'); this will resurrect the removed items for (ItemState descendant : stateMgr.getDescendantTransientItemStatesInAttic(state.getId())) { // dispose the transient state; this will indirectly // (through stateDiscarded listener method) resurrect // the wrapping Item instances stateMgr.disposeTransientItemStateInAttic(descendant); } } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.refresh(" + keepChanges + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRemoveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; /** * Session operation for removing a given item, optionally with constraint * checks enabled. */ class ItemRemoveOperation implements SessionWriteOperation { /** * The item to be removed. */ private final ItemImpl item; /** * Flag to enabled constraint checks */ private final boolean checks; public ItemRemoveOperation(ItemImpl item, boolean checks) { this.item = item; this.checks = checks; } public Object perform(SessionContext context) throws RepositoryException { // check if this is the root node if (item.getDepth() == 0) { throw new RepositoryException("Cannot remove the root node"); } NodeImpl parentNode = (NodeImpl) item.getParent(); if (checks) { ItemValidator validator = context.getItemValidator(); validator.checkRemove( item, CHECK_CONSTRAINTS | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); // Make sure the parent node is checked-out and // neither protected nor locked. validator.checkModify( parentNode, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS, Permission.NONE); } // delegate the removal of the child item to the parent node if (item.isNode()) { parentNode.removeChildNode((NodeId) item.getId()); } else { parentNode.removeChildProperty(item.getPrimaryPath().getName()); } return this; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.remove()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.StaleItemStateException; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The session operation triggered by {@link Item#save()}. */ class ItemSaveOperation implements SessionWriteOperation { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(ItemSaveOperation.class); private final ItemState state; public ItemSaveOperation(ItemState state) { this.state = state; } public Object perform(SessionContext context) throws RepositoryException { SessionItemStateManager stateMgr = context.getItemStateManager(); /** * build list of transient (i.e. new & modified) states that * should be persisted */ Collection dirty; try { dirty = getTransientStates(context.getItemStateManager()); } catch (ConcurrentModificationException e) { String msg = "Concurrent modification; session is closed"; log.error(msg, e); context.getSessionImpl().logout(); throw e; } if (dirty.size() == 0) { // no transient items, nothing to do here return this; } /** * build list of transient descendants in the attic * (i.e. those marked as 'removed') */ Collection removed = getRemovedStates(context.getItemStateManager()); // All affected item states. The keys are used to look up whether // an item is affected, and the values are iterated through below Map affected = new HashMap(dirty.size() + removed.size()); for (ItemState state : dirty) { affected.put(state.getId(), state); } for (ItemState state : removed) { affected.put(state.getId(), state); } /** * make sure that this save operation is totally 'self-contained' * and independent; items within the scope of this save operation * must not have 'external' dependencies; * (e.g. moving a node requires that the target node including both * old and new parents are saved) */ for (ItemState transientState : affected.values()) { if (transientState.isNode()) { NodeState nodeState = (NodeState) transientState; Set dependentIDs = new HashSet(); if (nodeState.hasOverlayedState()) { NodeState overlayedState = (NodeState) nodeState.getOverlayedState(); NodeId oldParentId = overlayedState.getParentId(); NodeId newParentId = nodeState.getParentId(); if (oldParentId != null) { if (newParentId == null) { // node has been removed, add old parents // to dependencies if (overlayedState.isShareable()) { dependentIDs.addAll(overlayedState.getSharedSet()); } else { dependentIDs.add(oldParentId); } } else { if (!oldParentId.equals(newParentId)) { // node has been moved to a new location, // add old and new parent to dependencies dependentIDs.add(oldParentId); dependentIDs.add(newParentId); } else { // parent id hasn't changed, check whether // the node has been renamed (JCR-1034) if (!affected.containsKey(newParentId) && stateMgr.hasTransientItemState(newParentId)) { try { NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); // check parent's renamed child node entries for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { if (cne.getId().equals(nodeState.getId())) { // node has been renamed, // add parent to dependencies dependentIDs.add(newParentId); } } } catch (ItemStateException ise) { // should never get here log.warn("failed to retrieve transient state: " + newParentId, ise); } } } } } } // removed child node entries for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // added child node entries for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { dependentIDs.add(cne.getId()); } // now walk through dependencies and check whether they // are within the scope of this save operation for (NodeId id : dependentIDs) { if (!affected.containsKey(id)) { // JCR-1359 workaround: check whether unresolved // dependencies originate from 'this' session; // otherwise ignore them if (stateMgr.hasTransientItemState(id) || stateMgr.hasTransientItemStateInAttic(id)) { // need to save dependency as well String msg = context.getItemManager().safeGetJCRPath(id) + " needs to be saved as well."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } // validate access and node type constraints // (this will also validate child removals) validateTransientItems(context, dirty, removed); // start the update operation try { stateMgr.edit(); } catch (IllegalStateException e) { throw new RepositoryException("Unable to start edit operation", e); } boolean succeeded = false; try { // process transient items marked as 'removed' removeTransientItems(context.getItemStateManager(), removed); // process transient items that have change in mixins processShareableNodes( context.getRepositoryContext().getNodeTypeRegistry(), dirty); // initialize version histories for new nodes (might generate new transient state) if (initVersionHistories(context, dirty)) { // re-build the list of transient states because the previous call // generated new transient state dirty = getTransientStates(context.getItemStateManager()); } // process 'new' or 'modified' transient states persistTransientItems(context.getItemManager(), dirty); // dispose the transient states marked 'new' or 'modified' // at this point item state data is pushed down one level, // node instances are disconnected from the transient // item state and connected to the 'overlayed' item state. // transient item states must be removed now. otherwise // the session item state provider will return an orphaned // item state which is not referenced by any node instance. for (ItemState transientState : dirty) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemState(transientState); } // end update operation stateMgr.update(); // update operation succeeded succeeded = true; } catch (StaleItemStateException e) { throw new InvalidItemStateException( "Unable to update a stale item: " + this, e); } catch (ItemStateException e) { throw new RepositoryException( "Unable to update item: " + this, e); } finally { if (!succeeded) { // update operation failed, cancel all modifications stateMgr.cancel(); // JCR-288: if an exception has been thrown during // update() the transient changes have already been // applied by persistTransientItems() and we need to // restore transient state, i.e. undo the effect of // persistTransientItems() restoreTransientItems(context, dirty); } } // now it is safe to dispose the transient states: // dispose the transient states marked 'removed'. // item states in attic are removed after store, because // the observation mechanism needs to build paths of removed // items in store(). for (ItemState transientState : removed) { // dispose the transient state, it is no longer used stateMgr.disposeTransientItemStateInAttic(transientState); } return this; } /** * Builds a list of transient (i.e. new or modified) item states that are * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getTransientStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { // list of transient states that should be persisted ArrayList dirty = new ArrayList(); if (state.isNode()) { // build list of 'new' or 'modified' descendants for (ItemState transientState : sism.getDescendantTransientItemStates(state.getId())) { // fail-fast test: check status of transient state switch (transientState.getStatus()) { case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: // add modified state to the list dirty.add(transientState); break; case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been " + "deleted externally: " + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been " + "removed externally: " + this); default: log.warn("Unexpected item state status: " + transientState.getStatus() + " of " + this); // ignore break; } } } // fail-fast test: check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { case ItemState.STATUS_EXISTING_MODIFIED: // add this item's state to the list dirty.add(state); break; case ItemState.STATUS_NEW: throw new RepositoryException( "Cannot save a new item: " + this); case ItemState.STATUS_STALE_DESTROYED: throw new InvalidItemStateException( "Item cannot be saved because it has been" + " deleted externally:" + this); case ItemState.STATUS_UNDEFINED: throw new InvalidItemStateException( "Item cannot be saved; it seems to have been" + " removed externally: " + this); default: log.warn("Unexpected item state status:" + state.getStatus() + " of " + this); // ignore break; } } return dirty; } /** * Builds a list of transient descendant item states in the attic * (i.e. those marked as 'removed') that are within the scope of * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException * @throws RepositoryException */ private Collection getRemovedStates( SessionItemStateManager sism) throws InvalidItemStateException, RepositoryException { if (state.isNode()) { ArrayList removed = new ArrayList(); for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); } removed.add(transientState); } return removed; } else { return Collections.emptyList(); } } /** * the following validations/checks are performed on transient items: * * for every transient item: * - if it is 'modified' or 'new' check the corresponding write permission. * - if it is 'removed' check the REMOVE permission * * for every transient node: * - if it is 'new' check that its node type satisfies the * 'required node type' constraint specified in its definition * - check if 'mandatory' child items exist * * for every transient property: * - check if the property value satisfies the value constraints * specified in the property's definition * * note that the protected flag is checked in Node.addNode/Node.remove * (for adding/removing child entries of a node), in * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) * and in Property.setValue (for properties to be modified). */ private void validateTransientItems( SessionContext context, Iterable dirty, Iterable removed) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); AccessManager accessMgr = context.getAccessManager(); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); // walk through list of dirty transient items and validate each for (ItemState itemState : dirty) { ItemDefinition def; if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState); } else { def = itemMgr.getDefinition((PropertyState) itemState); } /* check permissions for non-protected items. protected items are only added through API methods which need to assert that permissions are not violated. */ if (!def.isProtected()) { /* detect the effective set of modification: - new added node -> add_node perm on the child - new property added -> set_property permission - property modified -> set_property permission - modified nodes can be ignored for changes only included child-item addition or removal or changes of protected properties such as mixin-types which are covered separately note: removed items are checked later on. note: reordering of child nodes has been covered upfront as this information isn't available here. */ Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); boolean isGranted = true; if (itemState.isNode()) { if (itemState.getStatus() == ItemState.STATUS_NEW) { isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); } // else: modified node (see comment above) } else { // modified or new property: set_property permission isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); } if (!isGranted) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; log.debug(msg); throw new AccessDeniedException(msg); } } if (itemState.isNode()) { // the transient item is a node NodeState nodeState = (NodeState) itemState; ItemId id = nodeState.getNodeId(); NodeDefinition nodeDef = (NodeDefinition) def; // primary type NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType ent = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); /** * if the transient node was added (i.e. if it is 'new') or if * its primary type has changed, check its node type against the * required node type in its definition */ boolean primaryTypeChanged = nodeState.getStatus() == ItemState.STATUS_NEW; if (!primaryTypeChanged) { NodeState overlaid = (NodeState) nodeState.getOverlayedState(); if (overlaid != null) { Name newName = nodeState.getNodeTypeName(); Name oldName = overlaid.getNodeTypeName(); primaryTypeChanged = !newName.equals(oldName); } } if (primaryTypeChanged) { for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { Name ntName = ((NodeTypeImpl) ntReq).getQName(); if (!(pnt.getQName().equals(ntName) || pnt.isDerivedFrom(ntName))) { /** * the transient node's primary node type does not * satisfy the 'required primary types' constraint */ String msg = itemMgr.safeGetJCRPath(id) + " must be of node type " + ntReq.getName(); log.debug(msg); throw new ConstraintViolationException(msg); } } } // mandatory child properties for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { /** * todo FIXME workaround for mix:versionable: * the mandatory properties are initialized at a * later stage and might not exist yet */ continue; } String msg = itemMgr.safeGetJCRPath(id) + ": mandatory property " + pd.getName() + " does not exist"; if (!nodeState.hasPropertyName(pd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a property with the mandatory-name. make sure the property really has the expected mandatory property definition (and not another non-mandatory def, such as e.g. multivalued residual instead of single-value mandatory, named def). */ PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); ItemData childData = itemMgr.getItemData(pi, null, false); if (!childData.getDefinition().isMandatory()) { throw new ConstraintViolationException(msg); } } } // mandatory child nodes for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { String msg = itemMgr.safeGetJCRPath(id) + ": mandatory child node " + cnd.getName() + " does not exist"; if (!nodeState.hasChildNodeEntry(cnd.getName())) { log.debug(msg); throw new ConstraintViolationException(msg); } else { /* there exists a child node with the mandatory-name. make sure the node really has the expected mandatory node definition. */ boolean hasMandatoryChild = false; for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { ItemData childData = itemMgr.getItemData(cne.getId(), null, false); if (childData.getDefinition().isMandatory()) { hasMandatoryChild = true; break; } } if (!hasMandatoryChild) { throw new ConstraintViolationException(msg); } } } } else { // the transient item is a property PropertyState propState = (PropertyState) itemState; ItemId propId = propState.getPropertyId(); org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; /** * check value constraints * (no need to check value constraints of protected properties * as those are set by the implementation only, i.e. they * cannot be set by the user through the api) */ if (!def.isProtected()) { String[] constraints = propDef.getValueConstraints(); if (constraints != null) { InternalValue[] values = propState.getValues(); try { EffectiveNodeType.checkSetPropertyValueConstraints( propDef.unwrap(), values); } catch (RepositoryException e) { // repack exception for providing more verbose error message String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); log.debug(msg); throw new ConstraintViolationException(msg); } /** * need to manually check REFERENCE value constraints * as this requires a session (target node needs to * be checked) */ if (constraints.length > 0 && (propDef.getRequiredType() == PropertyType.REFERENCE || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { for (InternalValue internalV : values) { boolean satisfied = false; String constraintViolationMsg = null; try { NodeId targetId = internalV.getNodeId(); if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE && !itemMgr.itemExists(targetId)) { // target of weakref doesn;t exist, skip continue; } Node targetNode = session.getNodeById(targetId); /** * constraints are OR-ed, i.e. at least one * has to be satisfied */ for (String constrNtName : constraints) { /** * a [WEAK]REFERENCE value constraint specifies * the name of the required node type of * the target node */ if (targetNode.isNodeType(constrNtName)) { satisfied = true; break; } } if (!satisfied) { NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); String[] targetMixins = new String[mixinNodeTypes.length]; for (int j = 0; j < mixinNodeTypes.length; j++) { targetMixins[j] = mixinNodeTypes[j].getName(); } String targetMixinsString = Text.implode(targetMixins, ", "); String constraintsString = Text.implode(constraints, ", "); constraintViolationMsg = itemMgr.safeGetJCRPath(propId) + ": is constraint to [" + constraintsString + "] but references [primaryType=" + targetNode.getPrimaryNodeType().getName() + ", mixins=" + targetMixinsString + "]"; } } catch (RepositoryException re) { String msg = itemMgr.safeGetJCRPath(propId) + ": failed to check " + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") + " value constraint"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!satisfied) { log.debug(constraintViolationMsg); throw new ConstraintViolationException(constraintViolationMsg); } } } } } /** * no need to check the protected flag as this is checked * in PropertyImpl.setValue(Value) */ } } // walk through list of removed transient items and check REMOVE permission for (ItemState itemState : removed) { QItemDefinition def; try { if (itemState.isNode()) { def = itemMgr.getDefinition((NodeState) itemState).unwrap(); } else { def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); } } catch (ConstraintViolationException e) { // since identifier of assigned definition is not stored anymore // with item state (see JCR-2170), correct definition cannot be // determined for items which have been removed due to removal // of a mixin (see also JCR-2130 & JCR-2408) continue; } if (!def.isProtected()) { Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); // check REMOVE permission int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; if (!accessMgr.isGranted(path, permission)) { String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to remove item"; log.debug(msg); throw new AccessDeniedException(msg); } } } } /** * walk through list of transient items marked 'removed' and * definitively remove each one */ private void removeTransientItems( SessionItemStateManager sism, Iterable 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); } } /** * Process all items given in iterator and check whether mix:shareable * or (some derived node type) has been added or removed: * * If the mixin mix:shareable (or some derived node type), * then initialize the shared set inside the state. * If the mixin mix:shareable (or some derived node type) * has been removed, throw. * */ private void processShareableNodes( NodeTypeRegistry registry, Iterable states) throws RepositoryException { for (ItemState is : states) { if (is.isNode()) { NodeState ns = (NodeState) is; boolean wasShareable = false; if (ns.hasOverlayedState()) { NodeState old = (NodeState) ns.getOverlayedState(); EffectiveNodeType ntOld = getEffectiveNodeType(registry, old); wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); } EffectiveNodeType ntNew = getEffectiveNodeType(registry, ns); boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); if (!wasShareable && isShareable) { // mix:shareable has been added ns.addShare(ns.getParentId()); } else if (wasShareable && !isShareable) { // mix:shareable has been removed: not supported String msg = "Removing mix:shareable is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } } } /** * Initialises the version history of all new nodes of node type * mix:versionable. * * @param states * @return true if this call generated new transient state; otherwise false * @throws RepositoryException */ private boolean initVersionHistories( SessionContext context, Iterable states) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); // walk through list of transient items and search for new versionable nodes boolean createdTransientState = false; for (ItemState itemState : states) { if (itemState.isNode()) { NodeState nodeState = (NodeState) itemState; EffectiveNodeType nt = getEffectiveNodeType( context.getRepositoryContext().getNodeTypeRegistry(), nodeState); if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); InternalVersionManager vMgr = session.getInternalVersionManager(); /** * check if there's already a version history for that * node; this would e.g. be the case if a versionable * node had been exported, removed and re-imported with * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or * IMPORT_UUID_COLLISION_REPLACE_EXISTING; * otherwise create a new version history */ VersionHistoryInfo history = vMgr.getVersionHistory(session, nodeState, null); InternalValue historyId = InternalValue.create( history.getVersionHistoryId()); InternalValue versionId = InternalValue.create( history.getRootVersionId()); node.internalSetProperty( NameConstants.JCR_VERSIONHISTORY, historyId); node.internalSetProperty( NameConstants.JCR_BASEVERSION, versionId); node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); node.internalSetProperty( NameConstants.JCR_PREDECESSORS, new InternalValue[] { versionId }); createdTransientState = true; } } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { // we need to check the version manager for an existing // version history, since simple versioning does not // expose it's reference in a property InternalVersionManager vMgr = session.getInternalVersionManager(); vMgr.getVersionHistory(session, nodeState, null); // create isCheckedOutProperty if not already exists NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId(), false); if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { node.internalSetProperty( NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true)); createdTransientState = true; } } } } return createdTransientState; } /** * walk through list of transient items and persist each one */ private void persistTransientItems( ItemManager itemMgr, Iterable states) throws RepositoryException { for (ItemState state : states) { // persist state of transient item itemMgr.getItem(state.getId(), false).makePersistent(); } } /** * walk through list of transient states and re-apply transient changes */ private void restoreTransientItems( SessionContext context, Iterable items) { ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); for (ItemState itemState : items) { ItemId id = itemState.getId(); ItemImpl item; try { if (stateMgr.isItemStateInAttic(id)) { // If an item has been removed and then again created, the // item is lost after persistTransientItems() and the // TransientItemStateManager will bark because of a deleted // state in its attic. We therefore have to forge a new item // instance ourself. item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } else { try { item = itemMgr.getItem(id, false); } catch (ItemNotFoundException infe) { // itemState probably represents a 'new' item and the // ItemImpl instance wrapping it has already been gc'ed; // we have to re-create the ItemImpl instance item = itemMgr.createItemInstance(itemState); itemState.setStatus(ItemState.STATUS_NEW); } } // re-apply transient changes // for persistent nodes undo effect of item.makePersistent() if (item.isNode()) { NodeImpl node = (NodeImpl) item; node.restoreTransient((NodeState) itemState); } else { PropertyImpl prop = (PropertyImpl) item; prop.restoreTransient((PropertyState) itemState); } } catch (RepositoryException re) { // something went wrong, log exception and carry on String msg = itemMgr.safeGetJCRPath(id) + ": failed to restore transient state"; if (log.isDebugEnabled()) { log.warn(msg, re); } else { log.warn(msg); } } } } /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ private EffectiveNodeType getEffectiveNodeType( NodeTypeRegistry registry, NodeState state) throws RepositoryException { try { return registry.getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException e) { throw new RepositoryException( "Failed to build effective node type of node state " + state.getId(), e); } } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "item.save()"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemValidator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; 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.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for validating an item against constraints * specified by its definition. */ public class ItemValidator { /** * check access permissions */ public static final int CHECK_ACCESS = 1; /** * option to check lock status */ public static final int CHECK_LOCK = 2; /** * option to check checked-out status */ public static final int CHECK_CHECKED_OUT = 4; /** * check for referential integrity upon removal */ public static final int CHECK_REFERENCES = 8; /** * option to check if the item is protected by it's nt definition */ public static final int CHECK_CONSTRAINTS = 16; /** * option to check for pending changes on the session */ public static final int CHECK_PENDING_CHANGES = 32; /** * option to check for pending changes on the specified node */ public static final int CHECK_PENDING_CHANGES_ON_NODE = 64; /** * option to check for effective holds */ public static final int CHECK_HOLD = 128; /** * option to check for effective retention policies */ public static final int CHECK_RETENTION = 256; /** * Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(ItemValidator.class); /** * Component context of the associated session. */ protected final SessionContext context; /** * A bit mask of the checks that are currently enabled. All access to * this mask must be synchronized to ensure that only the thread that * uses the {@link #performRelaxed(SessionOperation, int)} method will * experience the effect of the relaxed set of checks. */ private int enabledChecks = ~0; /** * Creates a new ItemValidator instance. * * @param context component context of this session */ public ItemValidator(SessionContext context) { this.context = context; } /** * Performs the given session operation with the specified checks disabled. * * @param operation the session operation to be performed * @param checksToDisable bit mask of checks to be disabled * @return return value of the session operation * @throws RepositoryException if the operation could not be performed */ public synchronized T performRelaxed( SessionOperation operation, int checksToDisable) throws RepositoryException { int previousChecks = enabledChecks; try { enabledChecks &= ~checksToDisable; log.debug("Performing {} with checks [{}] disabled", operation, Integer.toBinaryString(~enabledChecks)); return operation.perform(context); } finally { enabledChecks = previousChecks; } } /** * Checks whether the given node state satisfies the constraints specified * by its primary and mixin node types. The following validations/checks are * performed: * * check if its node type satisfies the 'required node types' constraint * specified in its definition * check if all 'mandatory' child items exist * for every property: check if the property value satisfies the * value constraints specified in the property's definition * * * @param nodeState state of node to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(NodeState nodeState) throws ConstraintViolationException, RepositoryException { // effective primary node type NodeTypeRegistry registry = context.getNodeTypeRegistry(); EffectiveNodeType entPrimary = registry.getEffectiveNodeType(nodeState.getNodeTypeName()); // effective node type (primary type incl. mixins) EffectiveNodeType entPrimaryAndMixins = getEffectiveNodeType(nodeState); QNodeDefinition def = context.getItemManager().getDefinition(nodeState).unwrap(); // check if primary type satisfies the 'required node types' constraint for (Name requiredPrimaryType : def.getRequiredPrimaryTypes()) { if (!entPrimary.includesNodeType(requiredPrimaryType)) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": missing required primary type " + requiredPrimaryType; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory properties for (QPropertyDefinition pd : entPrimaryAndMixins.getMandatoryPropDefs()) { if (!nodeState.hasPropertyName(pd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory property " + pd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } // mandatory child nodes for (QItemDefinition cnd : entPrimaryAndMixins.getMandatoryNodeDefs()) { if (!nodeState.hasChildNodeEntry(cnd.getName())) { String msg = safeGetJCRPath(nodeState.getNodeId()) + ": mandatory child node " + cnd.getName() + " does not exist"; log.debug(msg); throw new ConstraintViolationException(msg); } } } /** * Checks whether the given property state satisfies the constraints * specified by its definition. The following validations/checks are * performed: * * check if the type of the property values does comply with the * requiredType specified in the property's definition * check if the property values satisfy the value constraints * specified in the property's definition * * * @param propState state of property to be validated * @throws ConstraintViolationException if any of the validations fail * @throws RepositoryException if another error occurs */ public void validate(PropertyState propState) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = context.getItemManager().getDefinition(propState).unwrap(); InternalValue[] values = propState.getValues(); int type = PropertyType.UNDEFINED; for (InternalValue value : values) { if (type == PropertyType.UNDEFINED) { type = value.getType(); } else if (type != value.getType()) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": inconsistent value types"); } if (def.getRequiredType() != PropertyType.UNDEFINED && def.getRequiredType() != type) { throw new ConstraintViolationException(safeGetJCRPath(propState.getPropertyId()) + ": requiredType constraint is not satisfied"); } } EffectiveNodeType.checkSetPropertyValueConstraints(def, values); } public synchronized void checkModify( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, false); } public synchronized void checkRemove( ItemImpl item, int options, int permissions) throws RepositoryException { checkCondition(item, options & enabledChecks, permissions, true); } private void checkCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { String msg = "Unable to perform operation. Session has pending changes."; log.debug(msg); throw new InvalidItemStateException(msg); } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { String msg = "Unable to perform operation. Node is protected."; log.debug(msg); throw new ConstraintViolationException(msg); } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { String msg = "Unable to perform operation. Node is checked-in."; log.debug(msg); throw new VersionException(msg); } } if ((options & CHECK_LOCK) == CHECK_LOCK) { checkLock(item); } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); context.getAccessManager().checkPermission(path, permissions); } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a hold."); } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { throw new RepositoryException("Unable to perform operation. Node is affected by a retention."); } } } public synchronized boolean canModify( ItemImpl item, int options, int permissions) throws RepositoryException { return hasCondition(item, options & enabledChecks, permissions, false); } private boolean hasCondition(ItemImpl item, int options, int permissions, boolean isRemoval) throws RepositoryException { if ((options & CHECK_PENDING_CHANGES) == CHECK_PENDING_CHANGES) { if (item.getSession().hasPendingChanges()) { return false; } } if ((options & CHECK_PENDING_CHANGES_ON_NODE) == CHECK_PENDING_CHANGES_ON_NODE) { if (item.isNode() && ((NodeImpl) item).hasPendingChanges()) { return false; } } if ((options & CHECK_CONSTRAINTS) == CHECK_CONSTRAINTS) { if (isProtected(item)) { return false; } } if ((options & CHECK_CHECKED_OUT) == CHECK_CHECKED_OUT) { NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); if (!node.isCheckedOut()) { return false; } } if ((options & CHECK_LOCK) == CHECK_LOCK) { try { checkLock(item); } catch (LockException e) { return false; } } if (permissions > Permission.NONE) { Path path = item.getPrimaryPath(); if (!context.getAccessManager().isGranted(path, permissions)) { return false; } } if ((options & CHECK_HOLD) == CHECK_HOLD) { if (hasHold(item, isRemoval)) { return false; } } if ((options & CHECK_RETENTION) == CHECK_RETENTION) { if (hasRetention(item, isRemoval)) { return false; } } return true; } private void checkLock(ItemImpl item) throws LockException, RepositoryException { if (item.isNew()) { // a new item needs no check return; } NodeImpl node = (item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent(); context.getWorkspace().getInternalLockManager().checkLock(node); } private boolean isProtected(ItemImpl item) throws RepositoryException { ItemDefinition def; if (item.isNode()) { def = ((Node) item).getDefinition(); } else { def = ((Property) item).getDefinition(); } return def.isProtected(); } private boolean hasHold(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveHold(path, checkParent); } private boolean hasRetention(ItemImpl item, boolean isRemoval) throws RepositoryException { if (item.isNew()) { return false; } Path path = item.getPrimaryPath(); if (!item.isNode()) { path = path.getAncestor(1); } boolean checkParent = (item.isNode() && isRemoval); return context.getSessionImpl().getRetentionRegistry().hasEffectiveRetention(path, checkParent); } //-------------------------------------------------< misc. helper methods > /** * Helper method that builds the effective (i.e. merged and resolved) * node type representation of the specified node's primary and mixin * node types. * * @param state * @return the effective node type * @throws RepositoryException */ public EffectiveNodeType getEffectiveNodeType(NodeState state) throws RepositoryException { try { return context.getNodeTypeRegistry().getEffectiveNodeType( state.getNodeTypeName(), state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "internal error: failed to build effective node type for node " + safeGetJCRPath(state.getNodeId()); log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Helper method that finds the applicable definition for a child node with * the given name and node type in the parent node's node type and * mixin types. * * @param name * @param nodeTypeName * @param parentState * @return a QNodeDefinition * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ public QNodeDefinition findApplicableNodeDefinition(Name name, Name nodeTypeName, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicableChildNodeDef( name, nodeTypeName, context.getNodeTypeRegistry()); } /** * Helper method that finds the applicable definition for a property with * the given name, type and multiValued characteristic in the parent node's * node type and mixin types. If there more than one applicable definitions * then the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * * * @param name * @param type * @param multiValued * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, boolean multiValued, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type, multiValued); } /** * Helper method that finds the applicable definition for a property with * the given name, type in the parent node's node type and mixin types. * Other than {@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)} * this method does not take the multiValued flag into account in the * selection algorithm. If there more than one applicable definitions then * the following rules are applied: * * named definitions are preferred to residual definitions * definitions with specific required type are preferred to definitions * with required type UNDEFINED * single-value definitions are preferred to multiple-value definitions * * * @param name * @param type * @param parentState * @return a QPropertyDefinition * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ public QPropertyDefinition findApplicablePropertyDefinition(Name name, int type, NodeState parentState) throws RepositoryException, ConstraintViolationException { EffectiveNodeType entParent = getEffectiveNodeType(parentState); return entParent.getApplicablePropertyDef(name, type); } /** * Failsafe conversion of internal Path to JCR path for use in * error messages etc. * * @param path path to convert * @return JCR path */ public String safeGetJCRPath(Path path) { try { return context.getJCRPath(path); } catch (NamespaceException e) { log.error("failed to convert {} to a JCR path", path); // return string representation of internal path as a fallback return path.toString(); } } /** * Failsafe translation of internal ItemId to JCR path for use * in error messages etc. * * @param id id to translate * @return JCR path */ public String safeGetJCRPath(ItemId id) { try { return safeGetJCRPath( context.getHierarchyManager().getPath(id)); } catch (ItemNotFoundException e) { // return string representation of id as a fallback return id.toString(); } catch (RepositoryException e) { log.error(id + ": failed to build path"); // return string representation of id as a fallback return id.toString(); } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; 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.commons.io.IOUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.security.principal.GroupPrincipals; import org.apache.jackrabbit.test.NotExecutableException; import org.apache.jackrabbit.test.RepositoryStub; import org.apache.jackrabbit.test.RepositoryStubException; /** * RepositoryStub implementation for Apache Jackrabbit. * * @since Apache Jackrabbit 1.6 */ public class JackrabbitRepositoryStub extends RepositoryStub { /** * Property for the repository configuration file. Defaults to * <repository home>/repository.xml if not specified. */ public static final String PROP_REPOSITORY_CONFIG = "org.apache.jackrabbit.repository.config"; /** * Property for the repository home directory. Defaults to * target/repository for convenience in Maven builds. */ public static final String PROP_REPOSITORY_HOME = "org.apache.jackrabbit.repository.home"; /** * Repository settings. */ private final Properties settings; /** * Map of repository instances. Key = repository home, value = repository * instance. */ private static final Map REPOSITORY_INSTANCES = new HashMap(); static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { synchronized (REPOSITORY_INSTANCES) { for (Repository repo : REPOSITORY_INSTANCES.values()) { if (repo instanceof RepositoryImpl) { ((RepositoryImpl) repo).shutdown(); } } } } })); } public static RepositoryContext getRepositoryContext( Repository repository) { synchronized (REPOSITORY_INSTANCES) { for (Repository r : REPOSITORY_INSTANCES.values()) { if (r == repository) { return ((RepositoryImpl) r).context; } } } throw new RuntimeException("Not a test repository: " + repository); } private static Properties getStaticProperties() { Properties properties = new Properties(); try { InputStream stream = getResource("JackrabbitRepositoryStub.properties"); try { properties.load(stream); } finally { stream.close(); } } catch (IOException e) { // TODO: Log warning } return properties; } private static InputStream getResource(String name) { return JackrabbitRepositoryStub.class.getResourceAsStream(name); } /** * Constructor as required by the JCR TCK. * * @param settings repository settings */ public JackrabbitRepositoryStub(Properties settings) { super(getStaticProperties()); // set some attributes on the sessions superuser.setAttribute("jackrabbit", "jackrabbit"); readwrite.setAttribute("jackrabbit", "jackrabbit"); readonly.setAttribute("jackrabbit", "jackrabbit"); // Repository settings this.settings = settings; } /** * Returns the configured repository instance. * * @return the configured repository instance. * @throws RepositoryStubException if an error occurs while * obtaining the repository instance. */ public synchronized Repository getRepository() throws RepositoryStubException { try { String dir = settings.getProperty(PROP_REPOSITORY_HOME); if (dir == null) { dir = new File("target", "repository").getAbsolutePath(); } else { dir = new File(dir).getAbsolutePath(); } String xml = settings.getProperty(PROP_REPOSITORY_CONFIG); if (xml == null) { xml = new File(dir, "repository.xml").getPath(); } return getOrCreateRepository(dir, xml); } catch (Exception e) { throw new RepositoryStubException("Failed to start repository", e); } } protected Repository createRepository(String dir, String xml) throws Exception { new File(dir).mkdirs(); if (!new File(xml).exists()) { InputStream input = getResource("repository.xml"); try { OutputStream output = new FileOutputStream(xml); try { IOUtils.copy(input, output); } finally { output.close(); } } finally { input.close(); } } RepositoryConfig config = RepositoryConfig.create(xml, dir); return RepositoryImpl.create(config); } protected Repository getOrCreateRepository(String dir, String xml) throws Exception { synchronized (REPOSITORY_INSTANCES) { Repository repo = REPOSITORY_INSTANCES.get(dir); if (repo == null) { repo = createRepository(dir, xml); Session session = repo.login(superuser); try { TestContentLoader loader = new TestContentLoader(); loader.loadTestContent(session); } finally { session.logout(); } REPOSITORY_INSTANCES.put(dir, repo); } return repo; } } @Override public Principal getKnownPrincipal(Session session) throws RepositoryException { Principal knownPrincipal = null; if (session instanceof SessionImpl) { for (Principal p : ((SessionImpl)session).getSubject().getPrincipals()) { if (!GroupPrincipals.isGroup(p)) { knownPrincipal = p; } } } if (knownPrincipal != null) { return knownPrincipal; } else { throw new RepositoryException("no applicable principal found"); } } private static Principal UNKNOWN_PRINCIPAL = new Principal() { public String getName() { return "an_unknown_user"; } }; @Override public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException { return UNKNOWN_PRINCIPAL; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitThreadPool.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.RejectedExecutionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread pool used by the repository. */ class JackrabbitThreadPool extends ScheduledThreadPoolExecutor { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory .getLogger(JackrabbitThreadPool.class); /** * Size of the per-repository thread pool. */ private static final int size = Runtime.getRuntime().availableProcessors() * 2; /** * The classloader used as the context classloader of threads in the pool. */ private static final ClassLoader loader = JackrabbitThreadPool.class.getClassLoader(); /** * Thread counter for generating unique names for the threads in the pool. */ private static final AtomicInteger counter = new AtomicInteger(1); /** * Thread factory for creating the threads in the pool */ private static final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { int count = counter.getAndIncrement(); String name = "jackrabbit-pool-" + count; Thread thread = new Thread(runnable, name); thread.setDaemon(true); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } thread.setContextClassLoader(loader); return thread; } }; /** * Handler for tasks for which no free thread is found within the pool. */ private static final RejectedExecutionHandler handler = new CallerRunsPolicy(); /** * Property to control the value at which the thread pool starts to schedule * the {@link LowPriorityTask} tasks for later execution. * * Set to 0 to disable the check * * Default value is 0 (check is disabled). * */ public static final String MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY = "org.apache.jackrabbit.core.JackrabbitThreadPool.maxLoadForLowPriorityTasks"; /** * @see #MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY */ private final static Integer maxLoadForLowPriorityTasks = getMaxLoadForLowPriorityTasks(); private static int getMaxLoadForLowPriorityTasks() { final int defaultMaxLoad = 75; int max = Integer.getInteger(MAX_LOAD_FOR_LOW_PRIORITY_TASKS_PROPERTY, defaultMaxLoad); if (max < 0 || max > 100) { return defaultMaxLoad; } return max; } /** * Queue where all the {@link LowPriorityTask} tasks go for later execution */ private final BlockingQueue lowPriorityTasksQueue = new LinkedBlockingQueue(); /** * Tasks that handles the scheduling and the execution of * {@link LowPriorityTask} tasks */ private final RetryLowPriorityTask retryTask; /** * Creates a new thread pool. */ public JackrabbitThreadPool() { super(size, factory, handler); retryTask = new RetryLowPriorityTask(this, lowPriorityTasksQueue); } @Override public void execute(Runnable command) { if (command instanceof LowPriorityTask) { scheduleLowPriority(command); return; } super.execute(command); } private void scheduleLowPriority(Runnable command) { if (isOverDefinedMaxLoad()) { lowPriorityTasksQueue.add(command); retryTask.retryLater(); return; } super.execute(command); } /** * compares the current load of the executor with the defined * {@link #maxLoadForLowPriorityTasks} parameter. * * Used to determine if the executor can handle additional * {@link LowPriorityTask} tasks. * * @return true if the load is under the * {@link #maxLoadForLowPriorityTasks} parameter */ private boolean isOverDefinedMaxLoad() { if (maxLoadForLowPriorityTasks == 0) { return false; } double currentLoad = ((double) getActiveCount()) / getPoolSize() * 100; return currentLoad > maxLoadForLowPriorityTasks; } /** * TEST ONLY * * @return the number of low priority tasks that are waiting in the queue */ int getPendingLowPriorityTaskCount() { return lowPriorityTasksQueue.size(); } private static final class RetryLowPriorityTask implements Runnable { /** * schedule interval in ms for delayed tasks */ private static final int LATER_MS = 50; private final JackrabbitThreadPool executor; private final BlockingQueue lowPriorityTasksQueue; /** * flag to indicate that another execute has been scheduled or is * currently running. */ private final AtomicBoolean retryPending; public RetryLowPriorityTask(JackrabbitThreadPool executor, BlockingQueue lowPriorityTasksQueue) { this.executor = executor; this.lowPriorityTasksQueue = lowPriorityTasksQueue; this.retryPending = new AtomicBoolean(false); } public void retryLater() { if (!retryPending.getAndSet(true)) { executor.schedule(this, LATER_MS, TimeUnit.MILLISECONDS); } } public void run() { int count = 0; while (!executor.isOverDefinedMaxLoad()) { Runnable r = lowPriorityTasksQueue.poll(); if (r == null) { log.debug("Executed {} low priority tasks.", count); break; } count++; executor.execute(r); } retryPending.set(false); if (!lowPriorityTasksQueue.isEmpty()) { log.debug( "Executor is under load, will schedule {} remaining tasks for {} ms later", lowPriorityTasksQueue.size(), LATER_MS); retryLater(); } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * LazyItemIterator is an id-based iterator that instantiates * the Items only when they are requested. * * Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
XASession
ItemNotFoundException
AccessDeniedException
permissionCheck
data
childId
ItemData
this.{@link #perform(SessionContext)}
mix:versionable
ItemValidator
QNodeDefinition
QPropertyDefinition
{@link #findApplicablePropertyDefinition(Name, int, boolean, NodeState)}
{@link #maxLoadForLowPriorityTasks}
LazyItemIterator
* Important: Items 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 * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List idList; /** parent node id (when returning children nodes) or null */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on {@link #next()} */ private Item next; /** * Creates a new LazyItemIterator instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new LazyItemIterator instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * * {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * * 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
{@link #next()}
* {@link #next} is set to the next available item in this iterator or to * null in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} *
* 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 * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LowPriorityTask.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 for low priority tasks (like text extraction) that can be scheduled * later based on the extractor's load * * @see JCR-3146. */ public interface LowPriorityTask extends Runnable { } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.cluster.NamespaceEventChannel; import org.apache.jackrabbit.core.cluster.NamespaceEventListener; import org.apache.jackrabbit.core.fs.BasedFileSystem; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import javax.jcr.AccessDeniedException; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; /** * A NamespaceRegistryImpl ... */ public class NamespaceRegistryImpl implements NamespaceRegistry, NamespaceEventListener, StringIndex { private static Logger log = LoggerFactory.getLogger(NamespaceRegistryImpl.class); /** * Special property key string to be used instead of an empty key to * avoid problems with Java implementations that have problems with * empty keys in property files. The selected value ({@value}) would be * invalid as either a namespace prefix or a URI, so there's little fear * of accidental collisions. * * @see JCR-888 */ private static final String EMPTY_KEY = ".empty.key"; private static final String NS_REG_RESOURCE = "ns_reg.properties"; private static final String NS_IDX_RESOURCE = "ns_idx.properties"; private static final HashSet reservedPrefixes = new HashSet(); private static final HashSet reservedURIs = new HashSet(); static { // reserved prefixes reservedPrefixes.add(Name.NS_XML_PREFIX); reservedPrefixes.add(Name.NS_XMLNS_PREFIX); // predefined (e.g. built-in) prefixes reservedPrefixes.add(Name.NS_REP_PREFIX); reservedPrefixes.add(Name.NS_JCR_PREFIX); reservedPrefixes.add(Name.NS_NT_PREFIX); reservedPrefixes.add(Name.NS_MIX_PREFIX); reservedPrefixes.add(Name.NS_SV_PREFIX); // reserved namespace URI's reservedURIs.add(Name.NS_XML_URI); reservedURIs.add(Name.NS_XMLNS_URI); // predefined (e.g. built-in) namespace URI's reservedURIs.add(Name.NS_REP_URI); reservedURIs.add(Name.NS_JCR_URI); reservedURIs.add(Name.NS_NT_URI); reservedURIs.add(Name.NS_MIX_URI); reservedURIs.add(Name.NS_SV_URI); } private HashMap prefixToURI = new HashMap(); private HashMap uriToPrefix = new HashMap(); private HashMap indexToURI = new HashMap(); private HashMap uriToIndex = new HashMap(); private final FileSystem nsRegStore; /** * Namespace event channel. */ private NamespaceEventChannel eventChannel; /** * Protected constructor: Constructs a new instance of this class. * * @param fs repository file system * @throws RepositoryException */ public NamespaceRegistryImpl(FileSystem fs) throws RepositoryException { this.nsRegStore = new BasedFileSystem(fs, "/namespaces"); load(); } /** * Clears all mappings. */ private void clear() { prefixToURI.clear(); uriToPrefix.clear(); indexToURI.clear(); uriToIndex.clear(); } /** * Adds a new mapping and automatically assigns a new index. * * @param prefix the namespace prefix * @param uri the namespace uri */ private void map(String prefix, String uri) { map(prefix, uri, null); } /** * Adds a new mapping and uses the given index if specified. * * @param prefix the namespace prefix * @param uri the namespace uri * @param idx the index or null. */ private void map(String prefix, String uri, Integer idx) { prefixToURI.put(prefix, uri); uriToPrefix.put(uri, prefix); if (!uriToIndex.containsKey(uri)) { if (idx == null) { // Need to use only 24 bits, since that's what // the BundleBinding class stores in bundles idx = uri.hashCode() & 0x00ffffff; while (indexToURI.containsKey(idx)) { idx = (idx + 1) & 0x00ffffff; } } indexToURI.put(idx, uri); uriToIndex.put(uri, idx); } } private void load() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); FileSystemResource idxFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { if (!propFile.exists()) { // clear existing mappings clear(); // default namespace (if no prefix is specified) map(Name.NS_EMPTY_PREFIX, Name.NS_DEFAULT_URI); // declare the predefined mappings // rep: map(Name.NS_REP_PREFIX, Name.NS_REP_URI); // jcr: map(Name.NS_JCR_PREFIX, Name.NS_JCR_URI); // nt: map(Name.NS_NT_PREFIX, Name.NS_NT_URI); // mix: map(Name.NS_MIX_PREFIX, Name.NS_MIX_URI); // sv: map(Name.NS_SV_PREFIX, Name.NS_SV_URI); // xml: map(Name.NS_XML_PREFIX, Name.NS_XML_URI); // persist mappings store(); return; } // check if index file exists Properties indexes = new Properties(); if (idxFile.exists()) { InputStream in = idxFile.getInputStream(); try { indexes.load(in); } finally { in.close(); } } InputStream in = propFile.getInputStream(); try { Properties props = new Properties(); props.load(in); // clear existing mappings clear(); // read mappings from properties for (Object p : props.keySet()) { String prefix = (String) p; String uri = props.getProperty(prefix); String idx = indexes.getProperty(escapePropertyKey(uri)); // JCR-888: Backwards compatibility check if (idx == null && uri.equals("")) { idx = indexes.getProperty(uri); } if (idx != null) { map(unescapePropertyKey(prefix), uri, Integer.decode(idx)); } else { map(unescapePropertyKey(prefix), uri); } } } finally { in.close(); } if (!idxFile.exists()) { store(); } } catch (Exception e) { String msg = "failed to load namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } } private void store() throws RepositoryException { FileSystemResource propFile = new FileSystemResource(nsRegStore, NS_REG_RESOURCE); try { propFile.makeParentDirs(); OutputStream os = propFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String prefix : prefixToURI.keySet()) { String uri = prefixToURI.get(prefix); props.setProperty(escapePropertyKey(prefix), uri); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry"; log.debug(msg); throw new RepositoryException(msg, e); } FileSystemResource indexFile = new FileSystemResource(nsRegStore, NS_IDX_RESOURCE); try { indexFile.makeParentDirs(); OutputStream os = indexFile.getOutputStream(); Properties props = new Properties(); // store mappings in properties for (String uri : uriToIndex.keySet()) { String index = uriToIndex.get(uri).toString(); props.setProperty(escapePropertyKey(uri), index); } try { props.store(os, null); } finally { // make sure stream is closed os.close(); } } catch (Exception e) { String msg = "failed to persist namespace registry index."; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Replaces an empty string with the special {@link #EMPTY_KEY} value. * * @see #unescapePropertyKey(String) * @param key property key * @return escaped property key */ private String escapePropertyKey(String key) { if (key.equals("")) { return EMPTY_KEY; } else { return key; } } /** * Converts the special {@link #EMPTY_KEY} value back to an empty string. * * @see #escapePropertyKey(String) * @param key property key * @return escaped property key */ private String unescapePropertyKey(String key) { if (key.equals(EMPTY_KEY)) { return ""; } else { return key; } } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NamespaceEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * Returns true if the specified uri is one of the reserved * URIs defined in this registry. * * @param uri The URI to test. * @return true if the specified uri is reserved; * false otherwise. */ public boolean isReservedURI(String uri) { return reservedURIs.contains(uri); } //-------------------------------------------------------< StringIndex >-- /** * Returns the index (i.e. stable prefix) for the given namespace URI. * * @param uri namespace URI * @return namespace index * @throws IllegalArgumentException if the namespace is not registered */ public int stringToIndex(String uri) { Integer idx = uriToIndex.get(uri); if (idx == null) { throw new IllegalArgumentException("Namespace not registered: " + uri); } return idx; } /** * Returns the namespace URI for a given index (i.e. stable prefix). * * @param idx namespace index * @return namespace URI * @throws IllegalArgumentException if the given index is invalid */ public String indexToString(int idx) { String uri = indexToURI.get(idx); if (uri == null) { throw new IllegalArgumentException("Invalid namespace index: " + idx); } return uri; } //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} */ public synchronized void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (prefix == null || uri == null) { throw new IllegalArgumentException("prefix/uri can not be null"); } if (Name.NS_EMPTY_PREFIX.equals(prefix) || Name.NS_DEFAULT_URI.equals(uri)) { throw new NamespaceException("default namespace is reserved and can not be changed"); } if (reservedURIs.contains(uri)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved URI"); } if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // special case: prefixes xml* if (prefix.toLowerCase().startsWith(Name.NS_XML_PREFIX)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": reserved prefix"); } // check if the prefix is a valid XML prefix if (!XMLChar.isValidNCName(prefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": invalid prefix"); } // check existing mappings String oldPrefix = uriToPrefix.get(uri); if (prefix.equals(oldPrefix)) { throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": mapping already exists"); } if (prefixToURI.containsKey(prefix)) { /** * prevent remapping of existing prefixes because this would in effect * remove the previously assigned namespace; * as we can't guarantee that there are no references to this namespace * (in names of nodes/properties/node types etc.) we simply don't allow it. */ throw new NamespaceException("failed to register namespace " + prefix + " -> " + uri + ": remapping existing prefixes is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(prefix, uri); if (eventChannel != null) { eventChannel.remapped(oldPrefix, prefix, uri); } // persist mappings store(); } /** * {@inheritDoc} */ public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { if (reservedPrefixes.contains(prefix)) { throw new NamespaceException("reserved prefix: " + prefix); } if (!prefixToURI.containsKey(prefix)) { throw new NamespaceException("unknown prefix: " + prefix); } /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } /** * {@inheritDoc} */ public String[] getPrefixes() throws RepositoryException { return prefixToURI.keySet().toArray(new String[prefixToURI.keySet().size()]); } /** * {@inheritDoc} */ public String[] getURIs() throws RepositoryException { return uriToPrefix.keySet().toArray(new String[uriToPrefix.keySet().size()]); } /** * {@inheritDoc} */ public String getURI(String prefix) throws NamespaceException { String uri = prefixToURI.get(prefix); if (uri == null) { throw new NamespaceException(prefix + ": is not a registered namespace prefix."); } return uri; } /** * {@inheritDoc} */ public String getPrefix(String uri) throws NamespaceException { String prefix = uriToPrefix.get(uri); if (prefix == null) { throw new NamespaceException(uri + ": is not a registered namespace uri."); } return prefix; } //-----------------------------------------------< NamespaceEventListener > /** * {@inheritDoc} */ public void externalRemap(String oldPrefix, String newPrefix, String uri) throws RepositoryException { if (newPrefix == null) { /** * as we can't guarantee that there are no references to the specified * namespace (in names of nodes/properties/node types etc.) we simply * don't allow it. */ throw new NamespaceException("unregistering namespaces is not supported."); } if (oldPrefix != null) { // remove old prefix mapping prefixToURI.remove(oldPrefix); uriToPrefix.remove(uri); } // add new prefix mapping map(newPrefix, uri); // persist mappings store(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.state.NodeState; /** * Data object representing a node. Used for non-shareable nodes or for the * first node in a shared set. For every share-sibling, NodeDataRef * is used instead. */ class NodeData extends AbstractNodeData { /** * Create a new instance of this class. * * @param state node state * @param itemMgr item manager */ NodeData(NodeState state, ItemManager itemMgr) { super(state, itemMgr); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeDataRef.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.ItemDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.state.ItemState; /** * Data object representing a node. Used for share-siblings of a shareable node * that is already loaded. */ class NodeDataRef extends AbstractNodeData { /** Referenced data object */ private final AbstractNodeData data; /** * Create a new instance of this class. * * @param data data to reference * @param primaryParentId primary parent id */ protected NodeDataRef(AbstractNodeData data, NodeId primaryParentId) { super(data.getId()); this.data = data; setPrimaryParentId(primaryParentId); } /** * {@inheritDoc} * * This implementation returns the state of the referenced data object. */ public ItemState getState() { return data.getState(); } /** * {@inheritDoc} * * This implementation sets the state of the referenced data object. */ protected void setState(ItemState state) { data.setState(state); } /** * {@inheritDoc} * * This implementation returns the definition of the referenced data object. * @throws RepositoryException if the definition cannot be retrieved. */ public ItemDefinition getDefinition() throws RepositoryException { return data.getDefinition(); } /** * {@inheritDoc} * * This implementation sets the definition of the referenced data object. */ protected void setDefinition(ItemDefinition definition) { data.setDefinition(definition); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.STRING; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CURRENT_LIFECYCLE_STATE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LIFECYCLE_POLICY; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_LIFECYCLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Calendar; import java.util.Collection; 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.Set; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidLifecycleTransitionException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; 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.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.ItemDefinition; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; 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.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 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; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ItemStateManager; 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.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.PathBuilder; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.util.ISO9075; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NodeImpl implements the Node interface. */ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); // flag set in status passed to getOrCreateProperty if property was created protected static final short CREATED = 0; /** node data (avoids casting ItemImpl.data) */ private final AbstractNodeData data; /** * Protected constructor. * * @param itemMgr the ItemManager that created this Node instance * @param sessionContext the component context of the associated session * @param data the node data */ protected NodeImpl( ItemManager itemMgr, SessionContext sessionContext, AbstractNodeData data) { super(itemMgr, sessionContext, data); this.data = data; // paranoid sanity check NodeTypeRegistry ntReg = sessionContext.getNodeTypeRegistry(); final NodeState state = data.getNodeState(); if (!ntReg.isRegistered(state.getNodeTypeName())) { /** * todo need proper way of handling inconsistent/corrupt node type references * e.g. 'flag' nodes that refer to non-registered node types */ log.warn("Fallback to nt:unstructured due to unknown node type '" + state.getNodeTypeName() + "' of " + this); data.getNodeState().setNodeTypeName(NameConstants.NT_UNSTRUCTURED); } List unknown = null; for (Name mixinName : state.getMixinTypeNames()) { if (!ntReg.isRegistered(mixinName)) { if (unknown == null) { unknown = new ArrayList(); } unknown.add(mixinName); log.warn("Ignoring unknown mixin type '" + mixinName + "' of " + this); } } if (unknown != null) { // ignore unknown mixin type names Set known = new HashSet(state.getMixinTypeNames()); known.removeAll(unknown); state.setMixinTypeNames(known); } } /** * Returns the node-state associated with this node. * * @return state associated with this node */ NodeState getNodeState() { return data.getNodeState(); } /** * Returns the id of the property at relPath or null * if no property exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. * * Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. * * Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
NamespaceRegistryImpl
NodeDataRef
NodeImpl
ItemImpl.data
relPath
* Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the id of the property at relPath or * null if no property exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getPropertyId(p); } /** * Returns the id of the node at relPath or null * if no node exists at relPath. *
* Note that access rights are not checked. * * @param relPath relative path of a (possible) node * @return the id of the node at relPath or * null if no node exists at relPath * @throws RepositoryException if relPath is not a valid * relative path */ protected NodeId resolveRelativeNodePath(String relPath) throws RepositoryException { Path p = resolveRelativePath(relPath); return getNodeId(p); } /** * Resolve a relative path given as string into a Path. If * a NameException occurs, it will be rethrown embedded * into a RepositoryException * * @param relPath relative path * @return Path object * @throws RepositoryException if an error occurs */ private Path resolveRelativePath(String relPath) throws RepositoryException { try { return sessionContext.getQPath(relPath); } catch (NameException e) { throw new RepositoryException( "Failed to resolve path " + relPath + " relative to " + this, e); } } /** * Returns the id of the node at p or null * if no node exists at p. *
NameException
RepositoryException
p
* Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private NodeId getNodeId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if node entry exists ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( p.getName(), p.getNormalizedIndex()); if (cne != null) { return cne.getId(); } else { return null; // there's no child node with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolveNodePath(p); } } /** * Returns the id of the property at p or null * if no node exists at p. *
* Note that access rights are not checked. * * @param p relative path of a (possible) node * @return the id of the node at p or * null if no node exists at p * @throws RepositoryException if relPath is not a valid * relative path */ private PropertyId getPropertyId(Path p) throws RepositoryException { if (p.getLength() == 1 && p.denotesName()) { // check if property entry exists NodeState thisState = data.getNodeState(); if (p.getIndex() == Path.INDEX_UNDEFINED && thisState.hasPropertyName(p.getName())) { return new PropertyId(thisState.getNodeId(), p.getName()); } else { return null; // there's no property with that name } } else { // build and resolve absolute path try { p = PathFactoryImpl.getInstance().create( getPrimaryPath(), p, true); } catch (RepositoryException re) { // failed to build canonical path return null; } return sessionContext.getHierarchyManager().resolvePropertyPath(p); } } /** * Determines if there are pending unsaved changes either on this * node or on any node or property in the subtree below it. * * @return true if there are pending unsaved changes, * false otherwise. * @throws RepositoryException if an error occurred */ protected boolean hasPendingChanges() throws RepositoryException { if (isTransient()) { return true; } return !stateMgr.getDescendantTransientItemStates(id).isEmpty(); } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { try { // make transient (copy-on-write) NodeState transientState = stateMgr.createTransientNodeState( (NodeState) stateMgr.getItemState(getId()), ItemState.STATUS_EXISTING_MODIFIED); // replace persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyImpl getOrCreateProperty(String name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { try { return getOrCreateProperty( sessionContext.getQName(name), type, multiValued, exactTypeMatch, status); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } } /** * @param name * @param type * @param multiValued * @param exactTypeMatch * @param status * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected synchronized PropertyImpl getOrCreateProperty(Name name, int type, boolean multiValued, boolean exactTypeMatch, BitSet status) throws ConstraintViolationException, RepositoryException { status.clear(); if (isNew() && !hasProperty(name)) { // this is a new node and the property does not exist yet // -> no need to check item manager PropertyDefinitionImpl def = getApplicablePropertyDefinition( name, type, multiValued, exactTypeMatch); PropertyImpl prop = createChildProperty(name, type, def); status.set(CREATED); return prop; } /* * Please note, that this implementation does not win a price for beauty * or speed. It's never a good idea to use exceptions for semantical * control flow. * However, compared to the previous version, this one is thread save * and makes the test/get block atomic in respect to transactional * commits. the test/set can still fail. * * Old Version: NodeState thisState = (NodeState) state; if (thisState.hasPropertyName(name)) { /** * the following call will throw ItemNotFoundException if the * current session doesn't have read access / return getProperty(name); } [...create block...] */ PropertyId propId = new PropertyId(getNodeId(), name); try { return (PropertyImpl) itemMgr.getItem(propId); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(name.toString()); } catch (ItemNotFoundException e) { // 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; 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; } } /** * Creates a new property with the given name and type hint and * property definition. If the given property definition is not of type * UNDEFINED, then it takes precedence over the * type hint. * * @param name the name of the property to create. * @param type the type hint. * @param def the associated property definition. * @return the property instance. * @throws RepositoryException if the property cannot be created. */ protected synchronized PropertyImpl createChildProperty(Name name, int type, PropertyDefinitionImpl def) throws RepositoryException { // create a new property state PropertyState propState; try { QPropertyDefinition propDef = def.unwrap(); if (def.getRequiredType() != PropertyType.UNDEFINED) { type = def.getRequiredType(); } propState = stateMgr.createTransientPropertyState(getNodeId(), name, ItemState.STATUS_NEW); propState.setType(type); propState.setMultiValued(propDef.isMultiple()); // compute system generated values if necessary String userId = sessionContext.getSessionImpl().getUserID(); new NodeTypeInstanceHandler(userId).setDefaultValues( propState, data.getNodeState(), propDef); } catch (ItemStateException ise) { String msg = "failed to add property " + name + " to " + this; log.debug(msg); throw new RepositoryException(msg, ise); } // create Property instance wrapping new property state // NOTE: since the property is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new property that is known to exist and // which has not been accessed before. PropertyImpl prop = (PropertyImpl) itemMgr.createItemInstance(propState); // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new property entry thisState.addPropertyName(name); return prop; } protected synchronized NodeImpl createChildNode(Name name, NodeTypeImpl nodeType, NodeId id) throws RepositoryException { // create a new node state NodeState nodeState = stateMgr.createTransientNodeState( id, nodeType.getQName(), getNodeId(), ItemState.STATUS_NEW); // create Node instance wrapping new node state NodeImpl node; try { // NOTE: since the node is not yet connected to its parent, avoid // calling ItemManager#getItem(ItemId) which may include a permission // check (with subsequent usage of the hierarachy-mgr -> error). // just let the mgr create the new node that is known to exist and // which has not been accessed before. node = (NodeImpl) itemMgr.createItemInstance(nodeState); } catch (RepositoryException re) { // something went wrong stateMgr.disposeTransientItemState(nodeState); // re-throw throw re; } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, nodeState.getNodeId()); // add 'auto-create' properties defined in node type for (PropertyDefinition aPda : nodeType.getAutoCreatedPropertyDefinitions()) { PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda; node.createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd); } // recursively add 'auto-create' child nodes defined in node type for (NodeDefinition aNda : nodeType.getAutoCreatedNodeDefinitions()) { NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda; node.createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(), null); } return node; } /** * * @param oldName * @param index * @param id * @param newName * @throws RepositoryException * @deprecated use #renameChildNode(NodeId, Name, boolean) */ @Deprecated protected void renameChildNode(Name oldName, int index, NodeId id, Name newName) throws RepositoryException { renameChildNode(id, newName, false); } /** * * @param id * @param newName * @param replace * @throws RepositoryException */ protected void renameChildNode(NodeId id, Name newName, boolean replace) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); if (replace) { // rename the specified child node by replacing the old // child node entry with a new one at the same relative position thisState.replaceChildNodeEntry(id, newName, id); } else { // rename the specified child node by removing the old and adding // a new child node entry. thisState.renameChildNodeEntry(id, newName); } } protected void removeChildProperty(Name propName) throws RepositoryException { // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove the property entry if (!thisState.removePropertyName(propName)) { String msg = "failed to remove property " + propName + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); itemMgr.getItem(propId).setRemoved(); } 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); if (entry == null) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } // notify target of removal try { NodeImpl childNode = itemMgr.getNode(childId, getNodeId()); childNode.onRemove(getNodeId()); } catch (ItemNotFoundException e) { boolean ignoreError = false; if (sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Node " + childId + " not found, ignore", e); ignoreError = true; } } if (!ignoreError) { throw e; } } // remove the child node entry if (!thisState.removeChildNodeEntry(childId)) { String msg = "failed to remove child " + childId + " of " + this; log.debug(msg); throw new RepositoryException(msg); } } protected void onRedefine(QNodeDefinition def) throws RepositoryException { NodeDefinitionImpl newDef = sessionContext.getNodeTypeManager().getNodeDefinition(def); // modify the state of 'this', i.e. the target node getOrCreateTransientItemState(); // set new definition data.setDefinition(newDef); } protected void onRemove(NodeId parentId) throws RepositoryException { // modify the state of 'this', i.e. the target node NodeState thisState = (NodeState) getOrCreateTransientItemState(); // remove this node from its shared set if (thisState.isShareable()) { if (thisState.removeShare(parentId) > 0) { // this state is still connected to some parents, so // leave the child node entries and properties // set state of this instance to 'invalid' data.setStatus(STATUS_INVALIDATED); // notify the item manager that this instance has been // temporarily invalidated itemMgr.itemInvalidated(id, data); return; } } if (thisState.hasChildNodeEntries()) { // remove child nodes // use temp array to avoid ConcurrentModificationException ArrayList tmp = new ArrayList(thisState.getChildNodeEntries()); // remove from tail to avoid problems with same-name siblings for (int i = tmp.size() - 1; i >= 0; i--) { ChildNodeEntry entry = tmp.get(i); // recursively remove child node NodeId childId = entry.getId(); //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId); try { /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ NodeImpl childNode = itemMgr.getNode(childId, getNodeId(), false); childNode.onRemove(thisState.getNodeId()); // remove the child node entry } catch (ItemNotFoundException e) { boolean ignoreError = false; if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(childId)) { log.warn("Child named " + entry.getName() + " (index " + entry.getIndex() + ", " + "node id " + childId + ") " + "not found when trying to remove " + getPath() + " " + "(node id " + getNodeId() + ") - ignored", e); ignoreError = true; } } if (!ignoreError) { throw e; } } thisState.removeChildNodeEntry(childId); } } // remove properties // use temp set to avoid ConcurrentModificationException HashSet tmp = new HashSet(thisState.getPropertyNames()); for (Name propName : tmp) { // remove the property entry thisState.removePropertyName(propName); // remove property PropertyId propId = new PropertyId(thisState.getNodeId(), propName); /* omit the read-permission check upon retrieving the child item as this is an internal call to remove the subtree which may contain (protected) child items which are not visible to the caller of the removal. the actual validation of the remove permission however is only executed during Item.save(). */ itemMgr.getItem(propId, false).setRemoved(); } // finally remove this node thisState.setParentId(null); setRemoved(); } void setMixinTypesProperty(Set mixinNames) throws RepositoryException { NodeState thisState = data.getNodeState(); // get or create jcr:mixinTypes property PropertyImpl prop; if (thisState.hasPropertyName(NameConstants.JCR_MIXINTYPES)) { prop = (PropertyImpl) itemMgr.getItem(new PropertyId(thisState.getNodeId(), NameConstants.JCR_MIXINTYPES)); } else { // find definition for the jcr:mixinTypes property and create property PropertyDefinitionImpl def = getApplicablePropertyDefinition( NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true, true); prop = createChildProperty(NameConstants.JCR_MIXINTYPES, PropertyType.NAME, def); } if (mixinNames.isEmpty()) { // purge empty jcr:mixinTypes property removeChildProperty(NameConstants.JCR_MIXINTYPES); return; } // call internalSetValue for setting the jcr:mixinTypes property // to avoid checking of the 'protected' flag InternalValue[] vals = new InternalValue[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int cnt = 0; while (iter.hasNext()) { vals[cnt++] = InternalValue.create(iter.next()); } prop.internalSetValue(vals, PropertyType.NAME); } /** * Returns the Names of this node's mixin types. * * @return a set of the Names of this node's mixin types. */ public Set getMixinTypeNames() { return data.getNodeState().getMixinTypeNames(); } /** * Returns the effective (i.e. merged and resolved) node type representation * of this node's primary and mixin node types. * * @return the effective node type * @throws RepositoryException if an error occurs */ public EffectiveNodeType getEffectiveNodeType() throws RepositoryException { try { return sessionContext.getNodeTypeRegistry().getEffectiveNodeType( data.getNodeState().getNodeTypeName(), data.getNodeState().getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, ntce); } } /** * Returns the applicable child node definition for a child node with the * specified name and node type. * * @param nodeName * @param nodeTypeName * @return * @throws ConstraintViolationException if no applicable child node definition * could be found * @throws RepositoryException if another error occurs */ protected NodeDefinitionImpl getApplicableChildNodeDefinition(Name nodeName, Name nodeTypeName) throws ConstraintViolationException, RepositoryException { NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); QNodeDefinition cnd = getEffectiveNodeType().getApplicableChildNodeDef( nodeName, nodeTypeName, sessionContext.getNodeTypeRegistry()); return ntMgr.getNodeDefinition(cnd); } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @param exactTypeMatch * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ protected PropertyDefinitionImpl getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued, boolean exactTypeMatch) throws ConstraintViolationException, RepositoryException { QPropertyDefinition pd; if (exactTypeMatch || type == PropertyType.UNDEFINED) { pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } else { try { // try to find a definition with matching type first pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, type, multiValued); } catch (ConstraintViolationException cve) { // none found, now try by ignoring the type pd = getEffectiveNodeType().getApplicablePropertyDef( propertyName, PropertyType.UNDEFINED, multiValued); } } return sessionContext.getNodeTypeManager().getPropertyDefinition(pd); } @Override protected void makePersistent() throws RepositoryException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } NodeState transientState = data.getNodeState(); NodeState localState = stateMgr.makePersistent(transientState); // swap transient state with local state data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { data.setPrimaryParentId(localState.getParentId()); } } protected void restoreTransient(NodeState transientState) throws RepositoryException { NodeState thisState = null; if (!isTransient()) { thisState = (NodeState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } thisState.setParentId(transientState.getParentId()); thisState.setNodeTypeName(transientState.getNodeTypeName()); } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { thisState = stateMgr.createTransientNodeState( (NodeId) transientState.getId(), transientState.getNodeTypeName(), transientState.getParentId(), NodeState.STATUS_NEW); data.setState(thisState); } } // re-apply transient changes thisState.setMixinTypeNames(transientState.getMixinTypeNames()); thisState.setChildNodeEntries(transientState.getChildNodeEntries()); thisState.setPropertyNames(transientState.getPropertyNames()); thisState.setSharedSet(transientState.getSharedSet()); thisState.setModCount(transientState.getModCount()); } /** * Same as {@link Node#addMixin(String)} except that it takes a * Name instead of a String. * * @see Node#addMixin(String) */ public void addMixin(Name mixinName) throws RepositoryException { perform(new AddMixinOperation(this, mixinName)); } /** * Same as {@link Node#removeMixin(String)} except that it takes a * Name instead of a String. * * @see Node#removeMixin(String) */ public void removeMixin(Name mixinName) throws RepositoryException { perform(new RemoveMixinOperation(this, mixinName)); } /** * Same as {@link Node#isNodeType(String)} except that it takes a * Name instead of a String. * * @param ntName name of node type * @return true if this node is of the specified node type; * otherwise false */ public boolean isNodeType(Name ntName) throws RepositoryException { // first do trivial checks without using type hierarchy Name primary = data.getNodeState().getNodeTypeName(); if (ntName.equals(primary)) { return true; } Set mixins = data.getNodeState().getMixinTypeNames(); if (mixins.contains(ntName)) { return true; } // check effective node type try { NodeTypeRegistry registry = sessionContext.getNodeTypeRegistry(); EffectiveNodeType type = registry.getEffectiveNodeType(primary, mixins); return type.includesNodeType(ntName); } catch (NodeTypeConflictException e) { String msg = "Failed to build effective node type for " + this; log.debug(msg); throw new RepositoryException(msg, e); } } /** * Checks various pre-conditions that are common to all * setProperty() methods. The checks performed are: * * this node must be checked-out * this node must not be locked by somebody else * * Note that certain checks are performed by the respective * Property.setValue() methods. * * @throws VersionException if this node is not checked-out * @throws LockException if this node is locked by somebody else * @throws RepositoryException if another error occurs * @see javax.jcr.Node#setProperty */ protected void checkSetProperty() throws VersionException, LockException, RepositoryException { // make sure this node is checked-out and is not locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); } /** * Sets the internal value of a property without checking any constraints. * * 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. * * 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
type
UNDEFINED
setProperty()
Property.setValue()
* 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. * @param name * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue value) throws ValueFormatException, RepositoryException { int type; if (value == null) { type = PropertyType.UNDEFINED; } else { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, false, true, status); try { if (value == null) { prop.internalSetValue(null, type); } else { prop.internalSetValue(new InternalValue[]{value}, type); } } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Sets the internal value of a property without checking any constraints. *
* 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. * * @param name * @param values * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values) throws ValueFormatException, RepositoryException { int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return internalSetProperty(name, values, type); } /** * Sets the internal value of a property without checking any constraints. *
* 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. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws RepositoryException */ protected Property internalSetProperty(Name name, InternalValue[] values, int type) throws ValueFormatException, RepositoryException { BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty(name, type, true, true, status); try { prop.internalSetValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(Name name) throws ItemNotFoundException, RepositoryException { return getNode(name, 1); } /** * Returns the child node of this node with the specified * name. * * @param name The name of the child node to retrieve. * @param index The index of the child node to retrieve (in the case of same-name siblings). * @return The child node with the specified name. * @throws ItemNotFoundException If no child node exists with the * specified name. * @throws RepositoryException If another error occurs. */ public NodeImpl getNode(final Name name, final int index) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public NodeImpl perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); if (cne != null) { try { return context.getItemManager().getNode( cne.getId(), getNodeId()); } catch (AccessDeniedException e) { throw new ItemNotFoundException(); } } else { throw new ItemNotFoundException(); } } public String toString() { return "node.getNode(" + name + "[" + index + "])"; } }); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(Name name) throws RepositoryException { return hasNode(name, 1); } /** * Indicates whether a child node with the specified name exists. * Returns true if the child node exists and false * otherwise. * * @param name The name of the child node. * @param index The index of the child node (in the case of same-name siblings). * @return true if the child node exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasNode(final Name name, final int index) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { ChildNodeEntry cne = data.getNodeState().getChildNodeEntry( name, index != 0 ? index : 1); return cne != null && context.getItemManager().itemExists(cne.getId()); } public String toString() { return "node.hasNode(" + name + "[" + index + "])"; } }); } /** * Returns the property of this node with the specified * name. * * @param name The name of the property to retrieve. * @return The property with the specified name. * @throws ItemNotFoundException If no property exists with the * specified name. * @throws RepositoryException If another error occurs. */ public PropertyImpl getProperty(final Name name) throws ItemNotFoundException, RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { try { return (PropertyImpl) context.getItemManager().getItem( new PropertyId(getNodeId(), name)); } catch (AccessDeniedException ade) { String n = context.getJCRName(name); throw new ItemNotFoundException( "Property " + n + " not found"); } } public String toString() { return "node.getProperty(" + name + ")"; } }); } /** * Indicates whether a property with the specified name exists. * Returns true if the property exists and false * otherwise. * * @param name The name of the property. * @return true if the property exists; false otherwise. * @throws RepositoryException If an unspecified error occurs. */ public boolean hasProperty(final Name name) throws RepositoryException { return perform(new SessionOperation() { public Boolean perform(SessionContext context) throws RepositoryException { return data.getNodeState().hasPropertyName(name) && context.getItemManager().itemExists( new PropertyId(getNodeId(), name)); } public String toString() { return "node.hasProperty(" + name + ")"; } }); } /** * Same as {@link Node#addNode(String, String)} except that * this method takes Name arguments instead of * Strings and has an additional uuid argument. * * Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
this
{@link Node#addNode(String, String)}
* Important Notice: This method is for internal use only! Passing * already assigned uuid's might lead to unexpected results and * data corruption in the worst case. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or null * if it should be determined automatically * @param id id of the new node or null if a new * id should be assigned * @return the newly added node * @throws RepositoryException if the node can not added */ // FIXME: This method should not be public public synchronized NodeImpl addNode( Name nodeName, Name nodeTypeName, NodeId id) throws RepositoryException { // check state of this instance sanityCheck(); Path nodePath = PathFactoryImpl.getInstance().create( getPrimaryPath(), nodeName, true); // Check the explicitly specified node type (if any) NodeTypeImpl nt = null; if (nodeTypeName != null) { nt = sessionContext.getNodeTypeManager().getNodeType(nodeTypeName); if (nt.isMixin()) { throw new ConstraintViolationException( "Unable to add a node with a mixin node type: " + sessionContext.getJCRName(nodeTypeName)); } else if (nt.isAbstract()) { throw new ConstraintViolationException( "Unable to add a node with an abstract node type: " + sessionContext.getJCRName(nodeTypeName)); } else { // adding a node with explicit specifying the node type name // requires the editing session to have nt_management privilege. sessionContext.getAccessManager().checkPermission( nodePath, Permission.NODE_TYPE_MNGMT); } } // Get the applicable child node definition for this node. NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(nodeName, nodeTypeName); } catch (RepositoryException e) { throw new ConstraintViolationException( "No child node definition for " + sessionContext.getJCRName(nodeName) + " found in " + this, e); } // Use default node type from child node definition if needed if (nt == null) { 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); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException( "This node already exists: " + itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeImpl existing = itemMgr.getNode(cne.getId(), getNodeId()); if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same-name siblings not allowed for " + existing); } } // check protected flag of parent (i.e. this) node and retention/hold // make sure this node is checked-out and not locked by another session. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // now do create the child node return createChildNode(nodeName, nt, id); } /** * Same as {@link Node#setProperty(String, Value[], int)} except * that this method takes a Name name argument instead of a * String. * * @param name * @param values * @param type * @return * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public PropertyImpl setProperty(Name name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, type, true); } /** * Same as {@link Node#setProperty(String, Value)} except that * this method takes a Name name argument instead of a * String. */ public PropertyImpl setProperty(Name name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(name, value, false)); } /** * @see ItemImpl#getQName() */ @Override public Name getQName() throws RepositoryException { HierarchyManager hierMgr = sessionContext.getHierarchyManager(); Name name; if (!isShareable()) { name = hierMgr.getName(id); } else { name = hierMgr.getName(getNodeId(), getParentId()); } return name; } /** * Returns the identifier of this Node. * * @return the id of this Node */ public NodeId getNodeId() { return (NodeId) id; } /** * 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 true if this node is access controlled and has a * rep:policy child; false 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 {@link Node#orderBefore(String, String)} except that * this method takes a Path.Element arguments instead of * Strings. * * @param srcName * @param dstName * @throws UnsupportedRepositoryOperationException * @throws VersionException * @throws ConstraintViolationException * @throws ItemNotFoundException * @throws LockException * @throws RepositoryException */ public synchronized void orderBefore(Path.Element srcName, Path.Element dstName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { // check state of this instance sanityCheck(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException( "child node ordering not supported on " + this); } // check arguments if (srcName.equals(dstName)) { // there's nothing to do return; } // check existence if (!hasNode(srcName.getName(), srcName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { srcName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = srcName.toString(); } catch (NamespaceException e) { name = srcName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } if (dstName != null && !hasNode(dstName.getName(), dstName.getIndex())) { String name; try { Path.Element[] path = new Path.Element[] { dstName }; name = sessionContext.getJCRPath(new PathBuilder(path).getPath()); } catch (NameException e) { name = dstName.toString(); } catch (NamespaceException e) { name = dstName.toString(); } throw new ItemNotFoundException( this + " has no child node with name " + name); } // make sure this node is checked-out and neither protected nor locked int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); /* make sure the session is allowed to reorder child nodes. since there is no specific privilege for reordering child nodes, test if the the node to be reordered can be removed and added, i.e. treating reorder similar to a move. TODO: properly deal with sns in which case the index would change upon reorder. */ AccessManager acMgr = sessionContext.getAccessManager(); PathBuilder pb = new PathBuilder(getPrimaryPath()); pb.addLast(srcName.getName(), srcName.getIndex()); Path childPath = pb.getPath(); if (!acMgr.isGranted(childPath, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to reorder child node " + sessionContext.getJCRPath(childPath) + "."; log.debug(msg); throw new AccessDeniedException(msg); } ArrayList list = new ArrayList(data.getNodeState().getChildNodeEntries()); int srcInd = -1, destInd = -1; for (int i = 0; i < list.size(); i++) { ChildNodeEntry entry = list.get(i); if (srcInd == -1) { if (entry.getName().equals(srcName.getName()) && (entry.getIndex() == srcName.getIndex() || srcName.getIndex() == 0 && entry.getIndex() == 1)) { srcInd = i; } } if (destInd == -1 && dstName != null) { if (entry.getName().equals(dstName.getName()) && (entry.getIndex() == dstName.getIndex() || dstName.getIndex() == 0 && entry.getIndex() == 1)) { destInd = i; if (srcInd != -1) { break; } } } else { if (srcInd != -1) { break; } } } // check if resulting order would be different to current order if (destInd == -1) { if (srcInd == list.size() - 1) { // no change, we're done return; } } else { if ((destInd - srcInd) == 1) { // no change, we're done return; } } // reorder list if (destInd == -1) { list.add(list.remove(srcInd)); } else { if (srcInd < destInd) { list.add(destInd, list.get(srcInd)); list.remove(srcInd); } else { list.add(destInd, list.remove(srcInd)); } } // modify the state of 'this', i.e. the parent node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setChildNodeEntries(list); } /** * Replaces the child node with the specified id * by a new child node with the same id and specified nodeName, * nodeTypeName and mixinNames. * * @param id id of the child node to be replaced * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type * @param mixinNames name of the new node's mixin types * * @return the new child node replacing the existing child * @throws ItemNotFoundException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ public synchronized NodeImpl replaceChildNode(NodeId id, Name nodeName, Name nodeTypeName, Name[] mixinNames) throws ItemNotFoundException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); Node existing = (Node) itemMgr.getItem(id); // 'replace' is actually a 'remove existing/add new' operation; // this unfortunately changes the order of this node's // child node entries (JCR-1055); // => backup list of child node entries beforehand in order // to restore it afterwards NodeState state = data.getNodeState(); ChildNodeEntry cneExisting = state.getChildNodeEntry(id); if (cneExisting == null) { throw new ItemNotFoundException( this + ": no child node entry with id " + id); } List cneList = new ArrayList(state.getChildNodeEntries()); // remove existing existing.remove(); // create new child node NodeImpl node = addNode(nodeName, nodeTypeName, id); if (mixinNames != null) { for (Name mixinName : mixinNames) { node.addMixin(mixinName); } } // fetch state again, as it changed while removing child state = data.getNodeState(); // restore list of child node entries (JCR-1055) if (cneExisting.getName().equals(nodeName)) { // restore original child node list state.setChildNodeEntries(cneList); } else { // replace child node entry with different name // but preserving original position state.removeAllChildNodeEntries(); for (ChildNodeEntry cne : cneList) { if (cne.getId().equals(id)) { // replace entry with different name state.addChildNodeEntry(nodeName, id); } else { state.addChildNodeEntry(cne.getName(), cne.getId()); } } } return node; } /** * Create a child node that is a clone of a shareable node. * * @param src shareable source node * @param name name of new node * @return child node * @throws ItemExistsException if there already is a child node with the * name given and the definition does not allow creating another one * @throws VersionException if this node is not checked out * @throws ConstraintViolationException if no definition is found in this * node that would allow creating the child node * @throws LockException if this node is locked * @throws RepositoryException if some other error occurs */ public synchronized NodeImpl clone(NodeImpl src, Name name) throws ItemExistsException, VersionException, ConstraintViolationException, LockException, RepositoryException { Path nodePath; try { nodePath = PathFactoryImpl.getInstance().create(getPrimaryPath(), name, true); } catch (MalformedPathException e) { // should never happen String msg = "internal error: invalid path " + this; log.debug(msg); throw new RepositoryException(msg, e); } // (1) make sure that parent node is checked-out // (2) check lock status // (3) check protected flag of parent (i.e. this) node int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS; sessionContext.getItemValidator().checkModify(this, options, Permission.NONE); // (4) check for name collisions NodeDefinitionImpl def; try { def = getApplicableChildNodeDefinition(name, null); } catch (RepositoryException re) { String msg = "no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } NodeState thisState = data.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); if (!((NodeImpl) itemMgr.getItem(newId)).getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(itemMgr.safeGetJCRPath(nodePath)); } } // (5) do clone operation NodeId parentId = getNodeId(); src.addShareParent(parentId); // (6) modify the state of 'this', i.e. the parent node NodeId srcId = src.getNodeId(); thisState = (NodeState) getOrCreateTransientItemState(); // add new child node entry thisState.addChildNodeEntry(name, srcId); return itemMgr.getNode(srcId, parentId); } // -----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return true; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { return perform(new SessionOperation() { public String perform(SessionContext context) throws RepositoryException { NodeId parentId = data.getNodeState().getParentId(); if (parentId == null) { return ""; // this is the root node } Name name; if (!isShareable()) { name = context.getHierarchyManager().getName(id); } else { name = context.getHierarchyManager().getName( getNodeId(), parentId); } return context.getJCRName(name); } public String toString() { return "node.getName()"; } }); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { NodeId parentId = getParentId(); if (parentId != null) { return (Node) context.getItemManager().getItem(parentId); } else { throw new ItemNotFoundException( "Root node doesn't have a parent"); } } public String toString() { return "node.getParent()"; } }); } //----------------------------------------------------------------< Node > /** * {@inheritDoc} */ public Node addNode(String relPath) throws RepositoryException { return addNodeWithUuid(relPath, null, null); } /** * {@inheritDoc} */ public Node addNode(String relPath, String nodeTypeName) throws RepositoryException { return addNodeWithUuid(relPath, nodeTypeName, null); } /** * Adds a node with the given UUID. You can only add a node with a UUID * that is not already assigned to another node in this workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String) * @param relPath path of the new node * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid(String relPath, String uuid) throws RepositoryException { return addNodeWithUuid(relPath, null, uuid); } /** * Adds a node with the given node type and UUID. You can only add a node * with a UUID that is not already assigned to another node in this * workspace. * * @since Apache Jackrabbit 1.6 * @see JCR-1972 * @see Node#addNode(String, String) * @param relPath path of the new node * @param nodeTypeName name of the new node's node type, * or null for automatic type assignment * @param uuid UUID of the new node, * or null for a random new UUID * @return the newly added node * @throws RepositoryException if the node can not be added */ public Node addNodeWithUuid( String relPath, String nodeTypeName, String uuid) throws RepositoryException { return perform(new AddNodeOperation(this, relPath, nodeTypeName, uuid)); } /** * {@inheritDoc} */ public void orderBefore(String srcName, String destName) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { Path.Element insertName; try { Path p = sessionContext.getQPath(srcName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + srcName); } insertName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + srcName; log.debug(msg); throw new RepositoryException(msg, e); } Path.Element beforeName; if (destName != null) { try { Path p = sessionContext.getQPath(destName); // p must be a relative path of length==depth==1 (to eliminate e.g. "..") if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("invalid name: " + destName); } beforeName = p.getNameElement(); } catch (NameException e) { String msg = "invalid name: " + destName; log.debug(msg); throw new RepositoryException(msg, e); } } else { beforeName = null; } orderBefore(insertName, beforeName); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values) throws RepositoryException { return setProperty(getQName(name), values, getType(values), false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, Value[] values, int type) throws RepositoryException { return setProperty(getQName(name), values, type, true); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] strings) throws RepositoryException { Value[] values = getValues(strings, STRING); return setProperty(getQName(name), values, STRING, false); } /** Wrapper around {@link #setProperty(Name, Value[], int, boolean)} */ public Property setProperty(String name, String[] values, int type) throws RepositoryException { Value[] converted = getValues(values, type); return setProperty(sessionContext.getQName(name), converted, type, true); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { return setProperty(name, getValueFactory().createValue(value)); } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value, int)} */ public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { return setProperty( name, getValueFactory().createValue(value, type), type); } else { return setProperty(name, (Value) null, type); } } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null && value.getType() != type) { value = ValueHelper.convert(value, type, getValueFactory()); } return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, true)); } /** Wrapper around {@link SetPropertyOperation} */ public Property setProperty(String name, Value value) throws RepositoryException { return sessionContext.getSessionState().perform( new SetPropertyOperation(sessionContext.getQName(name), value, false)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { return setProperty(name, getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, boolean value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, double value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, long value) throws RepositoryException { return setProperty(name, getValueFactory().createValue(value)); } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { return setProperty(name, (Value) null); } } /** Wrapper around {@link #setProperty(String, Value)} */ public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { try { return setProperty(name, getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { return setProperty(name, (Value) null); } } /** * Implementation for setProperty() using a single {@link * Value}. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and single-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed value to * that type. If that fails, then a {@link ValueFormatException} is thrown. */ private class SetPropertyOperation implements SessionWriteOperation { private final Name name; private final Value value; private final boolean enforceType; /** * @param name property name * @param value new value of the property, * or null to remove the property * @param enforceType true to enforce the value type */ public SetPropertyOperation( Name name, Value value, boolean enforceType) { this.name = name; this.value = value; this.enforceType = enforceType; } /** * @return the Property object set, * or null if this operation was used to remove * a property (by setting its value to null) * @throws ValueFormatException if value cannot be * converted to the specified type or * if the property already exists and * is multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ public PropertyImpl perform(SessionContext context) throws RepositoryException { itemSanityCheck(); // check pre-conditions for setting property checkSetProperty(); int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } BitSet status = new BitSet(); PropertyImpl property = getOrCreateProperty(name, type, false, enforceType, status); try { property.setValue(value); } catch (RepositoryException e) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property 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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.setProperty(" + name + ", " + value + ")"; } } /** * Implementation for setProperty() using a {@link Value} * array. The type of the returned property is enforced based on the * enforceType flag. If set to true, the returned * property is of the passed type if it didn't exist before. If set to * false, then the returned property may be of some other type, * but still must be based on an existing property definition for the given * name and multi-valued flag. The resulting type is taken from that * definition and the implementation tries to convert the passed values to * that type. If that fails, then a {@link ValueFormatException} is thrown. * * @param name the name of the property to set. * @param values the values to set. If null the property * is removed. * @param type the target type of the values to set. * @param enforceType if the target type is enforced. * @return the Property object set, or null if * this method was used to remove a property (by setting its value * to null). * @throws ValueFormatException if a value cannot be converted to * the specified type or if the * property already exists and is not * multi-valued. * @throws VersionException if this node is read-only due to a * checked-in node and this implementation * performs this validation immediately. * @throws LockException if a lock prevents the setting of * the property and this implementation * performs this validation immediately. * @throws ConstraintViolationException if the change would violate a * node-type or other constraint and * this implementation performs this * validation immediately. * @throws RepositoryException if another error occurs. */ protected PropertyImpl setProperty( final Name name, final Value[] values, final int type, final boolean enforceType) throws RepositoryException { return perform(new SessionOperation() { public PropertyImpl perform(SessionContext context) throws RepositoryException { // check pre-conditions for setting property checkSetProperty(); BitSet status = new BitSet(); PropertyImpl prop = getOrCreateProperty( name, type, true, enforceType, status); try { prop.setValue(values, type); } catch (RepositoryException re) { if (status.get(CREATED)) { // setting value failed, get rid of newly created property removeChildProperty(name); } // rethrow throw re; } return prop; } public String toString() { return "node.setProperty(...)"; } }); } /** * {@inheritDoc} */ public Node getNode(final String relPath) throws RepositoryException { return perform(new SessionOperation() { public Node perform(SessionContext context) throws RepositoryException { Path p = resolveRelativePath(relPath); NodeId id = getNodeId(p); if (id == null) { throw new PathNotFoundException(relPath); } // determine parent as mandated by path NodeId parentId = null; if (!p.denotesRoot()) { parentId = getNodeId(p.getAncestor(1)); } try { // if the node is shareable, it now returns the node // with the right parent if (parentId != null) { return itemMgr.getNode(id, parentId); } else { return (NodeImpl) itemMgr.getItem(id); } } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getNode(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public NodeIterator getNodes() throws RepositoryException { // IMPORTANT: an implementation of Node.getNodes() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public NodeIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildNodes((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list child nodes of " + NodeImpl.this, e); } } public String toString() { return "node.getNodes()"; } }); } /** * {@inheritDoc} */ public PropertyIterator getProperties() throws RepositoryException { // IMPORTANT: an implementation of Node.getProperties() must not use // a class derived from TraversingElementVisitor to traverse the // hierarchy because this would lead to an infinite recursion! return perform(new SessionOperation() { public PropertyIterator perform(SessionContext context) throws RepositoryException { try { return itemMgr.getChildProperties((NodeId) id); } catch (ItemNotFoundException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } catch (AccessDeniedException e) { throw new RepositoryException( "Failed to list properties of " + NodeImpl.this, e); } } public String toString() { return "node.getProperties()"; } }); } /** * {@inheritDoc} */ public Property getProperty(final String relPath) throws PathNotFoundException, RepositoryException { return perform(new SessionOperation() { public Property perform(SessionContext context) throws RepositoryException { PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { try { return (Property) itemMgr.getItem(id); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } } else { throw new PathNotFoundException(relPath); } } public String toString() { return "node.getProperty(" + relPath + ")"; } }); } /** * {@inheritDoc} */ public boolean hasNode(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); NodeId id = resolveRelativeNodePath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public boolean hasNodes() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasNodes respects the access rights * of this node's session, i.e. it will * return false if child nodes exist * but the session is not granted read-access */ return itemMgr.hasChildNodes((NodeId) id); } /** * {@inheritDoc} */ public boolean hasProperties() throws RepositoryException { // check state of this instance sanityCheck(); /** * hasProperties respects the access rights * of this node's session, i.e. it will * return false if properties exist * but the session is not granted read-access */ return itemMgr.hasChildProperties((NodeId) id); } /** * {@inheritDoc} */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { // check state of this instance sanityCheck(); try { return isNodeType(sessionContext.getQName(nodeTypeName)); } catch (NameException e) { throw new RepositoryException( "invalid node type name: " + nodeTypeName, e); } } /** * {@inheritDoc} */ public NodeType getPrimaryNodeType() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getNodeTypeManager().getNodeType( data.getNodeState().getNodeTypeName()); } /** * {@inheritDoc} */ public NodeType[] getMixinNodeTypes() throws RepositoryException { // check state of this instance sanityCheck(); Set mixinNames = data.getNodeState().getMixinTypeNames(); if (mixinNames.isEmpty()) { return new NodeType[0]; } NodeType[] nta = new NodeType[mixinNames.size()]; Iterator iter = mixinNames.iterator(); int i = 0; while (iter.hasNext()) { nta[i++] = sessionContext.getNodeTypeManager().getNodeType(iter.next()); } return nta; } /** Wrapper around {@link #addMixin(Name)}. */ public void addMixin(String mixinName) throws RepositoryException { try { addMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** Wrapper around {@link #removeMixin(Name)}. */ public void removeMixin(String mixinName) throws RepositoryException { try { removeMixin(sessionContext.getQName(mixinName)); } catch (NameException e) { throw new RepositoryException( "Invalid mixin type name: " + mixinName, e); } } /** * {@inheritDoc} */ public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException { // check state of this instance sanityCheck(); Name ntName = sessionContext.getQName(mixinName); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(ntName); if (!mixin.isMixin()) { return false; } int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; int permissions = Permission.NODE_TYPE_MNGMT; // special handling of mix:(simple)versionable. since adding the mixin alters // the version storage jcr:versionManagement privilege is required // in addition. if (NameConstants.MIX_VERSIONABLE.equals(ntName) || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(ntName)) { permissions |= Permission.VERSION_MNGMT; } if (!sessionContext.getItemValidator().canModify(this, options, permissions)) { return false; } final Name primaryTypeName = data.getNodeState().getNodeTypeName(); NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName); if (primaryType.isDerivedFrom(ntName)) { // mixin already inherited -> addMixin is allowed but has no effect. return true; } // build effective node type of mixins & primary type // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entExisting; try { // existing mixin's Set mixins = new HashSet(data.getNodeState().getMixinTypeNames()); // build effective node type representing primary type including existing mixin's entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins); if (entExisting.includesNodeType(ntName)) { // the existing mixins already include the mixin to be added. // addMixin would succeed without modifying the node. return true; } // add new mixin mixins.add(ntName); // try to build new effective node type (will throw in case of conflicts) ntReg.getEffectiveNodeType(primaryTypeName, mixins); } catch (NodeTypeConflictException ntce) { return false; } return true; } /** * {@inheritDoc} */ public boolean hasProperty(String relPath) throws RepositoryException { // check state of this instance sanityCheck(); PropertyId id = resolveRelativePropertyPath(relPath); if (id != null) { return itemMgr.itemExists(id); } else { return false; } } /** * {@inheritDoc} */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * {@inheritDoc} */ public NodeDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getNodeDefinition(); } /** * {@inheritDoc} */ public NodeIterator getNodes(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * {@inheritDoc} */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { // check state of this instance sanityCheck(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException(); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException(); } } /** * {@inheritDoc} */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { // check state of this instance sanityCheck(); if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } return getNodeId().toString(); } /** * {@inheritDoc} */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) RepositoryImpl rep = (RepositoryImpl) getSession().getRepository(); srcSession = rep.createSession( sessionContext.getSessionImpl().getSubject(), workspaceName); // search nearest ancestor that is referenceable NodeImpl m1 = this; while (m1.getDepth() != 0 && !m1.isNodeType(NameConstants.MIX_REFERENCEABLE)) { m1 = (NodeImpl) m1.getParent(); } // if root is common ancestor, corresponding path is same as ours if (m1.getDepth() == 0) { // check existence if (!srcSession.getItemManager().nodeExists(getPrimaryPath())) { throw new ItemNotFoundException("Node not found: " + this); } else { return getPath(); } } // get corresponding ancestor Node m2 = srcSession.getNodeByUUID(m1.getUUID()); // return path of m2, if m1 == n1 if (m1 == this) { return m2.getPath(); } String relPath; try { Path p = m1.getPrimaryPath().computeRelativePath(getPrimaryPath()); // use prefix mappings of srcSession relPath = sessionContext.getJCRPath(p); } catch (NameException be) { // should never get here... String msg = "internal error: failed to determine relative path"; log.error(msg, be); throw new RepositoryException(msg, be); } if (!m2.hasNode(relPath)) { throw new ItemNotFoundException(); } else { return m2.getNode(relPath).getPath(); } } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public int getIndex() throws RepositoryException { // check state of this instance sanityCheck(); NodeId parentId = getParentId(); if (parentId == null) { // the root node cannot have same-name siblings; always return 1 return 1; } try { NodeState parent = (NodeState) stateMgr.getItemState(parentId); ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId()); return parentEntry.getIndex(); } catch (ItemStateException ise) { // should never get here... String msg = "internal error: failed to determine index"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } //-------------------------------------------------------< shareable nodes > /** * Returns an iterator over all nodes that are in the shared set of this * node. If this node is not shared then the returned iterator contains * only this node. * * @return a NodeIterator * @throws RepositoryException if an error occurs. * @since JCR 2.0 */ public NodeIterator getSharedSet() throws RepositoryException { // check state of this instance sanityCheck(); ArrayList list = new ArrayList(); if (!isShareable()) { list.add(this); } else { NodeState state = data.getNodeState(); for (NodeId parentId : state.getSharedSet()) { list.add(itemMgr.getNode(getNodeId(), parentId)); } } return new NodeIteratorAdapter(list); } /** * A special kind of remove() that removes this node and every * other node in the shared set of this node. * * This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. * * All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. * * If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: * * this node is shareable * adding the given would create a share cycle * the given parent is already a share-parent * * @param parentId parent to add to the shared set * @throws RepositoryException if an error occurs */ void addShareParent(NodeId parentId) throws RepositoryException { // verify that we're shareable if (!isShareable()) { String msg = this + " is not shareable."; log.debug(msg); throw new RepositoryException(msg); } // detect share cycle NodeId srcId = getNodeId(); HierarchyManager hierMgr = sessionContext.getHierarchyManager(); if (parentId.equals(srcId) || hierMgr.isAncestor(srcId, parentId)) { String msg = "This would create a share cycle."; log.debug(msg); throw new RepositoryException(msg); } // quickly verify whether the share is already contained before creating // a transient state in vain NodeState state = data.getNodeState(); if (!state.containsShare(parentId)) { state = (NodeState) getOrCreateTransientItemState(); if (state.addShare(parentId)) { return; } } String msg = "Adding a shareable node twice to the same parent is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } /** * {@inheritDoc} * * Overridden to return a different path for shareable nodes. * * TODO SN: copies functionality in that is already available in * HierarchyManagerImpl, namely composing a path by * concatenating the parent path + this node's name and index: * rather use hierarchy manager to do this */ @Override public Path getPrimaryPath() throws RepositoryException { if (!isShareable()) { return super.getPrimaryPath(); } NodeId parentId = getParentId(); NodeImpl parentNode = (NodeImpl) getParent(); Path parentPath = parentNode.getPrimaryPath(); PathBuilder builder = new PathBuilder(parentPath); ChildNodeEntry entry = parentNode.getNodeState().getChildNodeEntry(getNodeId()); if (entry == null) { String msg = "failed to build path of " + id + ": " + parentId + " has no child entry for " + id; log.debug(msg); throw new ItemNotFoundException(msg); } // add to path if (entry.getIndex() == 1) { builder.addLast(entry.getName()); } else { builder.addLast(entry.getName(), entry.getIndex()); } return builder.getPath(); } //------------------------------< versioning support: public Node methods > /** * {@inheritDoc} */ public boolean isCheckedOut() throws RepositoryException { // check state of this instance sanityCheck(); // try shortcut first: // if current node is 'new' we can safely consider it checked-out since // otherwise it would had been impossible to add it in the first place if (isNew()) { return true; } // search nearest ancestor that is versionable // FIXME should not only rely on existence of jcr:isCheckedOut property // but also verify that node.isNodeType("mix:versionable")==true; // this would have a negative impact on performance though... try { NodeState state = getNodeState(); while (!state.hasPropertyName(JCR_ISCHECKEDOUT)) { ItemId parentId = state.getParentId(); if (parentId == null) { // root reached or out of hierarchy return true; } state = (NodeState) sessionContext.getItemStateManager().getItemState(parentId); } PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT); PropertyState ps = (PropertyState) sessionContext.getItemStateManager().getItemState(id); 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); } } /** * Returns the version manager of this workspace. */ private VersionManagerImpl getVersionManagerImpl() { return sessionContext.getWorkspace().getVersionManagerImpl(); } /** * {@inheritDoc} */ public void update(String srcWorkspaceName) throws RepositoryException { getVersionManagerImpl().update(this, srcWorkspaceName); } /** * Use {@link VersionManager#checkin(String)} instead */ @Deprecated public Version checkin() throws RepositoryException { return getVersionManagerImpl().checkin(getPath()); } /** * Use {@link VersionManagerImpl#checkin(String, Calendar)} instead * * @since Apache Jackrabbit 1.6 * @see JCR-1972 */ @Deprecated public Version checkin(Calendar created) throws RepositoryException { return getVersionManagerImpl().checkin(getPath(), created); } /** * Use {@link VersionManager#checkout(String)} instead */ @Deprecated public void checkout() throws RepositoryException { getVersionManagerImpl().checkout(getPath()); } /** * Use {@link VersionManager#merge(String, String, boolean)} instead */ @Deprecated public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { return getVersionManagerImpl().merge( getPath(), srcWorkspace, bestEffort); } /** * Use {@link VersionManager#cancelMerge(String, Version)} instead */ @Deprecated public void cancelMerge(Version version) throws RepositoryException { getVersionManagerImpl().cancelMerge(getPath(), version); } /** * Use {@link VersionManager#doneMerge(String, Version)} instead */ @Deprecated public void doneMerge(Version version) throws RepositoryException { getVersionManagerImpl().doneMerge(getPath(), version); } /** * Use {@link VersionManager#restore(String, String, boolean)} instead */ @Deprecated public void restore(String versionName, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(getPath(), versionName, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restore(this, version, removeExisting); } /** * Use {@link VersionManager#restore(String, Version, boolean)} instead */ @Deprecated public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { if (hasNode(relPath)) { getVersionManagerImpl().restore((NodeImpl) getNode(relPath), version, removeExisting); } else { getVersionManagerImpl().restore( getPath() + "/" + relPath, version, removeExisting); } } /** * Use {@link VersionManager#restoreByLabel(String, String, boolean)} * instead */ @Deprecated public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { getVersionManagerImpl().restoreByLabel( getPath(), versionLabel, removeExisting); } /** * Use {@link VersionManager#getVersionHistory(String)} instead */ @Deprecated public VersionHistory getVersionHistory() throws RepositoryException { return getVersionManagerImpl().getVersionHistory(getPath()); } /** * Use {@link VersionManager#getBaseVersion(String)} instead */ @Deprecated public Version getBaseVersion() throws RepositoryException { return getVersionManagerImpl().getBaseVersion(getPath()); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.lock(getPath(), isDeep, isSessionScoped, sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null); } /** * {@inheritDoc} */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.getLock(getPath()); } /** * {@inheritDoc} */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); lockMgr.unlock(getPath()); } /** * {@inheritDoc} */ public boolean holdsLock() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.holdsLock(getPath()); } /** * {@inheritDoc} */ public boolean isLocked() throws RepositoryException { // check state of this instance sanityCheck(); LockManager lockMgr = getSession().getWorkspace().getLockManager(); return lockMgr.isLocked(getPath()); } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else * @throws RepositoryException if some other error occurs * @deprecated */ @Deprecated protected void checkLock() throws LockException, RepositoryException { if (isNew()) { // a new node needs no check return; } sessionContext.getWorkspace().getInternalLockManager().checkLock(this); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public String getIdentifier() throws RepositoryException { return id.toString(); } /** * {@inheritDoc} */ public PropertyIterator getReferences(String name) throws RepositoryException { // check state of this instance sanityCheck(); try { if (stateMgr.hasNodeReferences(getNodeId())) { NodeReferences refs = stateMgr.getNodeReferences(getNodeId()); // refs.getReferences() returns a list of PropertyId's List idList = refs.getReferences(); if (name != null) { Name qName; try { qName = sessionContext.getQName(name); } catch (NameException e) { throw new RepositoryException("invalid property name: " + name, e); } ArrayList filteredList = new ArrayList(idList.size()); for (PropertyId propId : idList) { if (propId.getName().equals(qName)) { filteredList.add(propId); } } idList = filteredList; } return new LazyItemIterator(sessionContext, idList); } else { // there are no references, return empty iterator return PropertyIteratorAdapter.EMPTY; } } catch (ItemStateException e) { String msg = "Unable to retrieve REFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences() throws RepositoryException { // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } Value ref = getSession().getValueFactory().createValue(this, true); List props = new ArrayList(); QueryManagerImpl qm = (QueryManagerImpl) getSession().getWorkspace().getQueryManager(); for (Node n : qm.getWeaklyReferringNodes(this)) { for (PropertyIterator it = n.getProperties(); it.hasNext(); ) { Property p = it.nextProperty(); if (p.getType() == PropertyType.WEAKREFERENCE) { Collection refs; if (p.isMultiple()) { refs = Arrays.asList(p.getValues()); } else { refs = Collections.singleton(p.getValue()); } if (refs.contains(ref)) { props.add(p); } } } } return new PropertyIteratorAdapter(props); } /** * {@inheritDoc} */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { if (name == null) { return getWeakReferences(); } // check state of this instance sanityCheck(); // shortcut if node isn't referenceable if (!isNodeType(NameConstants.MIX_REFERENCEABLE)) { return PropertyIteratorAdapter.EMPTY; } try { StringBuilder stmt = new StringBuilder(); stmt.append("//*[@").append(ISO9075.encode(name)); stmt.append(" = '").append(data.getId()).append("']"); Query q = getSession().getWorkspace().getQueryManager().createQuery( stmt.toString(), Query.XPATH); QueryResult result = q.execute(); ArrayList l = new ArrayList(); for (NodeIterator nit = result.getNodes(); nit.hasNext();) { Node n = nit.nextNode(); l.add(n.getProperty(name)); } if (l.isEmpty()) { return PropertyIteratorAdapter.EMPTY; } else { return new PropertyIteratorAdapter(l); } } catch (RepositoryException e) { String msg = "Unable to retrieve WEAKREFERENCE properties that refer to " + id; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * {@inheritDoc} */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { // check state of this instance sanityCheck(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * {@inheritDoc} */ public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the primary type. int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, Permission.NODE_TYPE_MNGMT); final NodeState state = data.getNodeState(); if (state.getParentId() == null) { String msg = "changing the primary type of the root node is not supported"; log.debug(msg); throw new RepositoryException(msg); } Name ntName = sessionContext.getQName(nodeTypeName); if (ntName.equals(state.getNodeTypeName())) { log.debug("Node already has " + nodeTypeName + " as primary node type."); return; } NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin()) { throw new ConstraintViolationException(nodeTypeName + ": not a primary node type."); } else if (nt.isAbstract()) { throw new ConstraintViolationException(nodeTypeName + ": is an abstract node type."); } // build effective node type of new primary type & existing mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(ntName); entOld = ntReg.getEffectiveNodeType(state.getNodeTypeName()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(ntName, state.getMixinTypeNames()); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // get applicable definition for this node using new primary type QNodeDefinition nodeDef; try { NodeImpl parent = (NodeImpl) getParent(); nodeDef = parent.getApplicableChildNodeDefinition(getQName(), ntName).unwrap(); } catch (RepositoryException re) { String msg = this + ": no applicable definition found in parent node's node type"; log.debug(msg); throw new ConstraintViolationException(msg, re); } if (!nodeDef.equals(itemMgr.getDefinition(state).unwrap())) { onRedefine(nodeDef); } Set oldDefs = new HashSet(Arrays.asList(entOld.getAllItemDefs())); Set newDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); Set allDefs = new HashSet(Arrays.asList(entAll.getAllItemDefs())); // added child item definitions Set addedDefs = new HashSet(newDefs); addedDefs.removeAll(oldDefs); // referential integrity check boolean referenceableOld = entOld.includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entNew.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new primary type cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // do the actual modifications in content as mandated by the new primary type // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setNodeTypeName(ntName); // set jcr:primaryType property internalSetProperty(NameConstants.JCR_PRIMARYTYPE, InternalValue.create(ntName)); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { try { PropertyState propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); if (!allDefs.contains(itemMgr.getDefinition(propState).unwrap())) { // try to find new applicable definition first and // redefine property if possible try { PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (prop.getDefinition().isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); try { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); if (!allDefs.contains(itemMgr.getDefinition(nodeState).unwrap())) { // try to find new applicable definition first and // redefine node if possible try { NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); if (node.getDefinition().isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } } catch (ItemStateException ise) { String msg = entry.getName() + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new primary node // type and at the same time were not present with the old nt for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } /** * {@inheritDoc} */ public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * {@inheritDoc} */ public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = null; if (value != null) { v = getSession().getValueFactory().createValue(value); } return setProperty(name, v); } /** * Returns all allowed transitions from the current lifecycle state of * this node. * * The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
{@link Node#setProperty(String, Value[], int)}
{@link Node#setProperty(String, Value)}
{@link Node#orderBefore(String, String)}
Path.Element
mixinNames
enforceType
value
NodeIterator
remove()
* This removal must be done atomically, i.e., if one of the nodes cannot be * removed, the function throws the exception remove() would * have thrown in that case, and none of the nodes are removed. *
* If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeShare() * @see Item#remove() * @since JCR 2.0 */ public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); NodeIterator iter = getSharedSet(); while (iter.hasNext()) { iter.nextNode().removeShare(); } } /** * A special kind of remove() that removes this node, but does * not remove any other node in the shared set of this node. *
* All of the exceptions defined for remove() apply to this * function. In addition, a RepositoryException is thrown if * this node cannot be removed without removing another node in the shared * set of this node. *
* If this node is not shared this method removes only this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @see #removeSharedSet() * @see Item#remove() * @since JCR 2.0 */ public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // Standard remove() will remove just this node remove(); } /** * Helper method, returning a flag that indicates whether this node is * shareable. * * @return true if this node is shareable; * false otherwise. * @see NodeState#isShareable() */ boolean isShareable() { return data.getNodeState().isShareable(); } /** * Helper method, returning the parent id this node is attached to. If this * node is shareable, it returns the primary parent id (which remains * fixed since shareable nodes are not moveable). Otherwise returns the * underlying state's parent id. * * @return parent id */ public NodeId getParentId() { return data.getParentId(); } /** * Helper method, returning a flag indicating whether this node has * the given share-parent. * * @param parentId parent id * @return true if the node has the given shared parent; * false otherwise. */ boolean hasShareParent(NodeId parentId) { return data.getNodeState().containsShare(parentId); } /** * Add a share-parent to this node. This method checks, whether: *
* The lifecycle policy node referenced by the "jcr:lifecyclePolicy" * property is expected to contain a "transitions" node with a list of * child nodes, one for each transition. These transition nodes must * have single-valued string "from" and "to" properties that identify * the allowed source and target states of each transition. *
* Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @since Apache Jackrabbit 2.0 * @return allowed transitions for the current lifecycle state of this node * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws RepositoryException if a repository error occurs */ public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { if (isNodeType(NameConstants.MIX_LIFECYCLE)) { Node policy = getProperty(JCR_LIFECYCLE_POLICY).getNode(); String state = getProperty(JCR_CURRENT_LIFECYCLE_STATE).getString(); List targetStates = new ArrayList(); if (policy.hasNode("transitions")) { Node transitions = policy.getNode("transitions"); for (Node transition : JcrUtils.getChildNodes(transitions)) { String from = transition.getProperty("from").getString(); if (from.equals(state)) { String to = transition.getProperty("to").getString(); targetStates.add(to); } } } return targetStates.toArray(new String[targetStates.size()]); } else { throw new UnsupportedRepositoryOperationException( "Only nodes with mixin node type mix:lifecycle" + " may participate in a lifecycle: " + this); } } /** * Transitions this node through its lifecycle to the given target state. * * @since Apache Jackrabbit 2.0 * @see #getAllowedLifecycleTransistions() * @param transition target lifecycle state * @throws UnsupportedRepositoryOperationException * if this node does not have the mix:lifecycle mixin node type * @throws InvalidLifecycleTransitionException * if the given target state is not among the allowed * transitions from the current lifecycle state of this node * @throws RepositoryException if a repository error occurs */ public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { // getAllowedLifecycleTransitions checks for the mix:lifecycle mixin for (String target : getAllowedLifecycleTransistions()) { if (target.equals(transition)) { PropertyImpl property = getProperty(JCR_CURRENT_LIFECYCLE_STATE); property.internalSetValue( new InternalValue[] { InternalValue.create(target) }, PropertyType.STRING); property.save(); return; } } // No valid transition found throw new InvalidLifecycleTransitionException( "Invalid lifecycle transition \"" + transition + "\" for " + this); } /** * Assigns the given lifecycle policy to this node and sets the * current state to the one given. * * Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). * * Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
* Note that currently no special checks are made against the given * arguments, and that you will need to explicitly persist these changes * by calling save(). *
* Note that future versions of Apache Jackrabbit may well use different * lifecycle policy implementations. * * @param policy lifecycle policy node * @param state current lifecycle state * @throws RepositoryException if a repository error occurs */ public void assignLifecyclePolicy(Node policy, String state) throws RepositoryException { if (!(policy instanceof NodeImpl) || !((NodeImpl) policy).isNodeType(MIX_REFERENCEABLE)) { throw new RepositoryException( policy + " is not referenceable, so it can not be" + " used as a lifecycle policy"); } addMixin(MIX_LIFECYCLE); internalSetProperty( JCR_LIFECYCLE_POLICY, InternalValue.create(((NodeImpl) policy).getNodeId())); internalSetProperty( JCR_CURRENT_LIFECYCLE_STATE, InternalValue.create(state)); } //-------------------------------------------------------< JackrabbitNode > /** * {@inheritDoc} */ public void rename(String newName) throws RepositoryException { // check if this is the root node if (getDepth() == 0) { throw new RepositoryException("Cannot rename the root node"); } Name qName; try { qName = sessionContext.getQName(newName); } catch (NameException e) { throw new RepositoryException("invalid node name: " + newName, e); } NodeImpl parent = (NodeImpl) getParent(); // check for name collisions NodeImpl existing = null; try { existing = parent.getNode(qName); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(); } catch (ItemNotFoundException infe) { // no name collision, fall through } // verify that parent node // - is checked-out // - is not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; sessionContext.getItemValidator().checkRemove(parent, options, Permission.NONE); sessionContext.getItemValidator().checkModify(parent, options, Permission.NONE); // check constraints // get applicable definition of renamed target node NodeTypeImpl nt = (NodeTypeImpl) getPrimaryNodeType(); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl newTargetDef; try { newTargetDef = parent.getApplicableChildNodeDefinition(qName, nt.getQName()); } catch (RepositoryException re) { String msg = safeGetJCRPath() + ": no definition found in parent node's node type for renamed node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException( "Same name siblings not allowed: " + existing); } // check permissions: // 1. on the parent node the session must have permission to manipulate the child-entries AccessManager acMgr = sessionContext.getAccessManager(); if (!acMgr.isGranted(parent.getPrimaryPath(), qName, Permission.MODIFY_CHILD_NODE_COLLECTION)) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // 2. in case of nt-changes the session must have permission to change // the primary node type on this node itself. if (!nt.getName().equals(newTargetDef.getName()) && !(acMgr.isGranted(getPrimaryPath(), Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to rename node " + safeGetJCRPath() + " to " + newName; log.debug(msg); throw new AccessDeniedException(msg); } // change definition onRedefine(newTargetDef.unwrap()); // delegate to parent parent.renameChildNode(getNodeId(), qName, true); } /** * {@inheritDoc} */ public void setMixins(String[] mixinNames) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check state of this instance sanityCheck(); NodeTypeManagerImpl ntMgr = sessionContext.getNodeTypeManager(); Set newMixins = new HashSet(); for (String name : mixinNames) { Name qName = sessionContext.getQName(name); if (! ntMgr.getNodeType(qName).isMixin()) { throw new RepositoryException( sessionContext.getJCRName(qName) + " is not a mixin node type"); } newMixins.add(qName); } // make sure this node is checked-out, neither protected nor locked and // the editing session has sufficient permission to change the mixin types. // special handling of mix:(simple)versionable. since adding the // mixin alters the version storage jcr:versionManagement privilege // is required in addition. int permissions = Permission.NODE_TYPE_MNGMT; if (newMixins.contains(MIX_VERSIONABLE) || newMixins.contains(MIX_SIMPLE_VERSIONABLE)) { permissions |= Permission.VERSION_MNGMT; } int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD; sessionContext.getItemValidator().checkModify(this, options, permissions); final NodeState state = data.getNodeState(); // build effective node type of primary type & new mixin's // in order to detect conflicts NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry(); EffectiveNodeType entNew, entOld, entAll; try { entNew = ntReg.getEffectiveNodeType(newMixins); entOld = ntReg.getEffectiveNodeType(state.getMixinTypeNames()); // try to build new effective node type (will throw in case of conflicts) entAll = ntReg.getEffectiveNodeType(state.getNodeTypeName(), newMixins); } catch (NodeTypeConflictException ntce) { throw new ConstraintViolationException(ntce.getMessage()); } // added child item definitions Set addedDefs = new HashSet(Arrays.asList(entNew.getAllItemDefs())); addedDefs.removeAll(Arrays.asList(entOld.getAllItemDefs())); // referential integrity check boolean referenceableOld = getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE); boolean referenceableNew = entAll.includesNodeType(NameConstants.MIX_REFERENCEABLE); if (referenceableOld && !referenceableNew) { // node would become non-referenceable; // make sure no references exist PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException( "the new mixin types cannot be set as it would render " + "this node 'non-referenceable' while it is still being " + "referenced through at least one property of type REFERENCE"); } } // gather currently assigned definitions *before* doing actual modifications Map oldDefs = new HashMap(); for (Name name : getNodeState().getPropertyNames()) { PropertyId id = new PropertyId(getNodeId(), name); try { PropertyState propState = (PropertyState) stateMgr.getItemState(id); oldDefs.put(id, itemMgr.getDefinition(propState)); } catch (ItemStateException ise) { String msg = name + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } for (ChildNodeEntry cne : getNodeState().getChildNodeEntries()) { try { NodeState nodeState = (NodeState) stateMgr.getItemState(cne.getId()); oldDefs.put(cne.getId(), itemMgr.getDefinition(nodeState)); } catch (ItemStateException ise) { String msg = cne + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // now do the actual modifications in content as mandated by the new mixins // modify the state of this node NodeState thisState = (NodeState) getOrCreateTransientItemState(); thisState.setMixinTypeNames(newMixins); // set jcr:mixinTypes property setMixinTypesProperty(newMixins); // walk through properties and child nodes and change definition as necessary // use temp set to avoid ConcurrentModificationException HashSet set = new HashSet(thisState.getPropertyNames()); for (Name propName : set) { PropertyState propState = null; try { propState = (PropertyState) stateMgr.getItemState( new PropertyId(thisState.getNodeId(), propName)); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(propState); } catch (ConstraintViolationException cve) { // no suitable definition found for this property // try to find new applicable definition first and // redefine property if possible try { if (oldDefs.get(propState.getId()).isProtected()) { // remove 'orphaned' protected properties immediately removeChildProperty(propName); continue; } PropertyDefinitionImpl pdi = getApplicablePropertyDefinition( propName, propState.getType(), propState.isMultiValued(), false); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(propState.getId()); if (pdi.getRequiredType() != PropertyType.UNDEFINED && pdi.getRequiredType() != propState.getType()) { // value conversion required if (propState.isMultiValued()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), pdi.getRequiredType(), getSession().getValueFactory()); // redefine property prop.onRedefine(pdi.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(pdi.unwrap()); } // update collection of added definitions addedDefs.remove(pdi.unwrap()); } catch (ValueFormatException vfe) { // value conversion failed, remove it removeChildProperty(propName); } catch (ConstraintViolationException cve1) { // no suitable definition found for this property, // remove it removeChildProperty(propName); } } catch (ItemStateException ise) { String msg = propName + ": failed to retrieve property state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // use temp array to avoid ConcurrentModificationException ArrayList list = new ArrayList(thisState.getChildNodeEntries()); // start from tail to avoid problems with same-name siblings for (int i = list.size() - 1; i >= 0; i--) { ChildNodeEntry entry = list.get(i); NodeState nodeState = null; try { nodeState = (NodeState) stateMgr.getItemState(entry.getId()); // the following call triggers ConstraintViolationException // if there isn't any suitable definition anymore itemMgr.getDefinition(nodeState); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node // try to find new applicable definition first and // redefine node if possible try { if (oldDefs.get(nodeState.getId()).isProtected()) { // remove 'orphaned' protected child node immediately removeChildNode(entry.getId()); continue; } NodeDefinitionImpl ndi = getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId()); // redefine node node.onRedefine(ndi.unwrap()); // update collection of added definitions addedDefs.remove(ndi.unwrap()); } catch (ConstraintViolationException cve1) { // no suitable definition found for this child node, // remove it removeChildNode(entry.getId()); } } catch (ItemStateException ise) { String msg = entry + ": failed to retrieve node state"; log.error(msg, ise); throw new RepositoryException(msg, ise); } } // create items that are defined as auto-created by the new mixins // and at the same time were not present with the old mixins for (QItemDefinition def : addedDefs) { if (def.isAutoCreated()) { if (def.definesNode()) { NodeDefinitionImpl ndi = ntMgr.getNodeDefinition((QNodeDefinition) def); createChildNode(def.getName(), (NodeTypeImpl) ndi.getDefaultPrimaryType(), null); } else { PropertyDefinitionImpl pdi = ntMgr.getPropertyDefinition((QPropertyDefinition) def); createChildProperty(pdi.unwrap().getName(), pdi.getRequiredType(), pdi); } } } } //--------------------------------------------------------------< Object > /** * Return a string representation of this node for diagnostic purposes. * * @return "node /path/to/item" */ public String toString() { return "node " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeTypeInstanceHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.Calendar; import java.util.Set; import javax.jcr.RepositoryException; 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.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; /** * The NodeTypeInstanceHandler is used to provide or initialize * system protected properties (or child nodes). * */ public class NodeTypeInstanceHandler { /** * Default user id in the case where the creating user cannot be determined. */ public static final String DEFAULT_USERID = "system"; /** * userid to use for the "*By" autocreated properties */ private final String userId; /** * Creates a new node type instance handler. * @param userId the user id. if null, {@value #DEFAULT_USERID} is used. */ public NodeTypeInstanceHandler(String userId) { this.userId = userId == null ? DEFAULT_USERID : userId; } /** * Sets the system-generated or node type -specified default values * of the given property. If such values are not specified, then the * property is not modified. * * @param property property state * @param parent parent node state * @param def property definition * @throws RepositoryException if the default values could not be created */ public void setDefaultValues( PropertyState property, NodeState parent, QPropertyDefinition def) throws RepositoryException { InternalValue[] values = computeSystemGeneratedPropertyValues(parent, def); if (values == null && def.getDefaultValues() != null) { values = InternalValue.create(def.getDefaultValues()); } if (values != null) { property.setValues(values); } } /** * Computes the values of well-known system (i.e. protected) properties. * * @param parent the parent node state * @param def the definition of the property to compute * @return the computed values */ public InternalValue[] computeSystemGeneratedPropertyValues(NodeState parent, QPropertyDefinition def) { InternalValue[] genValues = null; Name name = def.getName(); Name declaringNT = def.getDeclaringNodeType(); if (NameConstants.JCR_UUID.equals(name)) { // jcr:uuid property of the mix:referenceable node type if (NameConstants.MIX_REFERENCEABLE.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(parent.getNodeId().toString())}; } } else if (NameConstants.JCR_PRIMARYTYPE.equals(name)) { // jcr:primaryType property (of any node type) genValues = new InternalValue[]{InternalValue.create(parent.getNodeTypeName())}; } else if (NameConstants.JCR_MIXINTYPES.equals(name)) { // jcr:mixinTypes property (of any node type) Set mixins = parent.getMixinTypeNames(); genValues = new InternalValue[mixins.size()]; int i = 0; for (Name n : mixins) { genValues[i++] = InternalValue.create(n); } } else if (NameConstants.JCR_CREATED.equals(name)) { // jcr:created property of a version or a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT) || NameConstants.NT_VERSION.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_CREATEDBY.equals(name)) { // jcr:createdBy property of a mix:created if (NameConstants.MIX_CREATED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_LASTMODIFIED.equals(name)) { // jcr:lastModified property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(Calendar.getInstance())}; } } else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)) { // jcr:lastModifiedBy property of a mix:lastModified if (NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) { genValues = new InternalValue[]{InternalValue.create(userId)}; } } else if (NameConstants.JCR_ETAG.equals(name)) { // jcr:etag property of a mix:etag if (NameConstants.MIX_ETAG.equals(declaringNT)) { // TODO: provide real implementation genValues = new InternalValue[]{InternalValue.create("")}; } } return genValues; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyData.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.nodetype.PropertyDefinition; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.state.PropertyState; /** * Data object representing a property. */ public class PropertyData extends ItemData { /** * Create a new instance of this class. * * @param state associated property state * @param itemMgr item manager */ PropertyData(PropertyState state, ItemManager itemMgr) { super(state, itemMgr); } /** * Return the associated property state. * * @return property state */ public PropertyState getPropertyState() { return (PropertyState) getState(); } /** * Return the associated property definition. * * @return property definition * @throws RepositoryException if the definition cannot be retrieved. */ public PropertyDefinition getPropertyDefinition() throws RepositoryException { return (PropertyDefinition) getDefinition(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.NAME; import static javax.jcr.PropertyType.PATH; import static javax.jcr.PropertyType.REFERENCE; import static javax.jcr.PropertyType.STRING; import static javax.jcr.PropertyType.UNDEFINED; import static javax.jcr.PropertyType.WEAKREFERENCE; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION; import java.io.InputStream; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemState; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PropertyImpl implements the Property interface. */ public class PropertyImpl extends ItemImpl implements Property { private static Logger log = LoggerFactory.getLogger(PropertyImpl.class); /** property data (avoids casting ItemImpl.data) */ private final PropertyData data; /** * Package private constructor. * * @param itemMgr the ItemManager that created this Property * @param sessionContext the component context of the associated session * @param data the property data */ PropertyImpl( ItemManager itemMgr, SessionContext sessionContext, PropertyData data) { super(itemMgr, sessionContext, data); this.data = data; // value will be read on demand } /** * Checks that this property is valid (session not closed, property not * removed, etc.) and returns the underlying property state if all is OK. * * @return property state * @throws RepositoryException if the property is not valid */ private PropertyState getPropertyState() throws RepositoryException { // JCR-1272: Need to get the state reference now so it // doesn't get invalidated after the sanity check ItemState state = getItemState(); sanityCheck(); return (PropertyState) state; } @Override protected synchronized ItemState getOrCreateTransientItemState() throws RepositoryException { synchronized (data) { if (!isTransient()) { // make transient (copy-on-write) try { PropertyState transientState = stateMgr.createTransientPropertyState( data.getPropertyState(), ItemState.STATUS_EXISTING_MODIFIED); // swap persistent with transient state data.setState(transientState); } catch (ItemStateException ise) { String msg = "failed to create transient state"; log.debug(msg); throw new RepositoryException(msg, ise); } } return getItemState(); } } @Override protected void makePersistent() throws InvalidItemStateException { if (!isTransient()) { log.debug(this + " (" + id + "): there's no transient state to persist"); return; } PropertyState transientState = data.getPropertyState(); PropertyState persistentState = (PropertyState) transientState.getOverlayedState(); if (persistentState == null) { // this property is 'new' try { persistentState = stateMgr.createNew(transientState); } catch (ItemStateException e) { throw new InvalidItemStateException(e); } } synchronized (persistentState) { // check staleness of transient state first if (transientState.isStale()) { String msg = this + ": the property cannot be saved because it has" + " been modified externally."; log.debug(msg); throw new InvalidItemStateException(msg); } // copy state from transient state persistentState.setType(transientState.getType()); persistentState.setMultiValued(transientState.isMultiValued()); persistentState.setValues(transientState.getValues()); // make state persistent stateMgr.store(persistentState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); // swap transient state with persistent state data.setState(persistentState); // reset status data.setStatus(STATUS_NORMAL); } protected void restoreTransient(PropertyState transientState) throws RepositoryException { PropertyState thisState = null; if (!isTransient()) { thisState = (PropertyState) getOrCreateTransientItemState(); if (transientState.getStatus() == ItemState.STATUS_NEW && thisState.getStatus() != ItemState.STATUS_NEW) { thisState.setStatus(ItemState.STATUS_NEW); stateMgr.disconnectTransientItemState(thisState); } } else { // JCR-2503: Re-create transient state in the state manager, // because it was removed synchronized (data) { try { thisState = stateMgr.createTransientPropertyState( transientState.getParentId(), transientState.getName(), PropertyState.STATUS_NEW); data.setState(thisState); } catch (ItemStateException e) { throw new RepositoryException(e); } } } // reapply transient changes thisState.setType(transientState.getType()); thisState.setMultiValued(transientState.isMultiValued()); thisState.setValues(transientState.getValues()); thisState.setModCount(transientState.getModCount()); } protected void onRedefine(QPropertyDefinition def) throws RepositoryException { PropertyDefinitionImpl newDef = sessionContext.getNodeTypeManager().getPropertyDefinition(def); data.setDefinition(newDef); } /** * Determines the length of the given value. * * @param value value whose length should be determined * @return the length of the given value * @throws RepositoryException if an error occurs * @see javax.jcr.Property#getLength() * @see javax.jcr.Property#getLengths() */ protected long getLength(InternalValue value) throws RepositoryException { long length; switch (value.getType()) { case NAME: case PATH: String str = ValueFormat.getJCRString(value, sessionContext); length = str.length(); break; default: length = value.getLength(); break; } return length; } /** * Checks various pre-conditions that are common to all * setValue() methods. The checks performed are: * * parent node must be checked-out * property must not be protected * parent node must not be locked by somebody else * property must be multi-valued when set to an array of values * (and vice versa) * * * @param multipleValues flag indicating whether the property is about to * be set to an array of values * @throws ValueFormatException if a single-valued property is set to an * array of values (and vice versa) * @throws VersionException if the parent node is not checked-out * @throws LockException if the parent node is locked by somebody else * @throws ConstraintViolationException if the property is protected * @throws RepositoryException if another error occurs * @see javax.jcr.Property#setValue */ protected void checkSetValue(boolean multipleValues) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { NodeImpl parent = (NodeImpl) getParent(false); // check multi-value flag if (multipleValues != isMultiple()) { String msg = (multipleValues) ? "Single-valued property can not be set to an array of values:" : "Multivalued property can not be set to a single value (an array of length one is OK): "; throw new ValueFormatException(msg + this); } // check protected flag and for retention/hold sessionContext.getItemValidator().checkModify( this, CHECK_CONSTRAINTS, Permission.NONE); // make sure the parent is checked-out and neither locked nor under retention sessionContext.getItemValidator().checkModify( parent, CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_HOLD | CHECK_RETENTION, Permission.NONE); } /** * @param values * @param type * @throws ConstraintViolationException * @throws RepositoryException */ protected void internalSetValue(InternalValue[] values, int type) throws ConstraintViolationException, RepositoryException { // check for null value if (values == null) { // setting a property to null removes it automatically ((NodeImpl) getParent()).removeChildProperty(((PropertyId) id).getName()); return; } ArrayList list = new ArrayList(); // compact array (purge null entries) for (InternalValue v : values) { if (v != null) { list.add(v); } } values = list.toArray(new InternalValue[list.size()]); // modify the state of this property PropertyState thisState = (PropertyState) getOrCreateTransientItemState(); // free old values as necessary InternalValue[] oldValues = thisState.getValues(); if (oldValues != null) { for (InternalValue old : oldValues) { if (old != null && old.getType() == BINARY) { // make sure temporarily allocated data is discarded // before overwriting it old.discard(); } } } // set new values thisState.setValues(values); // set type if (type == UNDEFINED) { // fallback to default type type = STRING; } thisState.setType(type); } protected Node getParent(boolean checkPermission) throws RepositoryException { return (Node) itemMgr.getItem(getPropertyState().getParentId(), checkPermission); } /** * Same as {@link Property#setValue(String)} except that * this method takes a Name instead of a String * value. * * @param name * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name name) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } if (name == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * Same as {@link Property#setValue(String[])} except that * this method takes an array of Name instead of * String values. * * @param names * @throws ValueFormatException * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException */ public void setValue(Name[] names) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = NAME; } InternalValue[] internalValues = null; // convert to internal values of correct type if (names != null) { internalValues = new InternalValue[names.length]; for (int i = 0; i < names.length; i++) { Name name = names[i]; InternalValue internalValue = null; if (name != null) { if (reqType != NAME) { // type conversion required Value targetValue = ValueHelper.convert( ValueFormat.getJCRValue(InternalValue.create(name), sessionContext, getSession().getValueFactory()), reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetValue, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create(name); } } internalValues[i] = internalValue; } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ @Override public Name getQName() { return ((PropertyId) id).getName(); } /** * Returns the internal values of a multi-valued property. * * @return array of values * @throws ValueFormatException if this property is not multi-valued * @throws RepositoryException */ public InternalValue[] internalGetValues() throws RepositoryException { final PropertyDefinition definition = data.getPropertyDefinition(); if (isMultiple()) { return getPropertyState().getValues(); } else { throw new ValueFormatException( this + " is a single-valued property," + " so it's value can not be retrieved as an array"); } } /** * Returns the internal value of a single-valued property. * * @return value * @throws ValueFormatException if this property is not single-valued * @throws RepositoryException */ public InternalValue internalGetValue() throws RepositoryException { if (isMultiple()) { throw new ValueFormatException( this + " is a multi-valued property," + " so it's values can only be retrieved as an array"); } else { InternalValue[] values = getPropertyState().getValues(); if (values.length > 0) { return values[0]; } else { // should never be the case, but being a little paranoid can't hurt... throw new RepositoryException(this + ": single-valued property with no value"); } } } //-------------------------------------------------------------< Property > public Value[] getValues() throws RepositoryException { InternalValue[] internals = internalGetValues(); Value[] values = new Value[internals.length]; for (int i = 0; i < internals.length; i++) { values[i] = ValueFormat.getJCRValue(internals[i], sessionContext, getSession().getValueFactory()); } return values; } public Value getValue() throws RepositoryException { try { return ValueFormat.getJCRValue(internalGetValue(), sessionContext, getSession().getValueFactory()); } catch (RuntimeException e) { String msg = "Internal error while retrieving value of " + this; log.error(msg, e); throw new RepositoryException(msg, e); } } /** Wrapper around {@link #getValue()} */ public String getString() throws RepositoryException { return getValue().getString(); } /** Wrapper around {@link #getValue()} */ public InputStream getStream() throws RepositoryException { final Binary binary = getValue().getBinary(); // make sure binary is disposed after stream had been consumed return new AutoCloseInputStream(binary.getStream()) { @Override public void close() throws IOException { super.close(); binary.dispose(); } }; } /** Wrapper around {@link #getValue()} */ public long getLong() throws RepositoryException { return getValue().getLong(); } /** Wrapper around {@link #getValue()} */ public double getDouble() throws RepositoryException { return getValue().getDouble(); } /** Wrapper around {@link #getValue()} */ public Calendar getDate() throws RepositoryException { return getValue().getDate(); } /** Wrapper around {@link #getValue()} */ public boolean getBoolean() throws RepositoryException { return getValue().getBoolean(); } public Node getNode() throws ValueFormatException, RepositoryException { Session session = getSession(); Value value = getValue(); int type = value.getType(); switch (type) { case REFERENCE: case WEAKREFERENCE: return session.getNodeByUUID(value.getString()); case PATH: case NAME: String path = value.getString(); Path p = sessionContext.getQPath(path); boolean absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(path) : getParent().getNode(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } case STRING: try { Value refValue = ValueHelper.convert(value, REFERENCE, session.getValueFactory()); return session.getNodeByUUID(refValue.getString()); } catch (RepositoryException e) { // try if STRING value can be interpreted as PATH value Value pathValue = ValueHelper.convert(value, PATH, session.getValueFactory()); p = sessionContext.getQPath(pathValue.getString()); absolute = p.isAbsolute(); try { return (absolute) ? session.getNode(pathValue.getString()) : getParent().getNode(pathValue.getString()); } catch (PathNotFoundException e1) { throw new ItemNotFoundException(pathValue.getString()); } } default: throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE"); } } public Property getProperty() throws RepositoryException { Value value = getValue(); Value pathValue = ValueHelper.convert(value, PATH, getSession().getValueFactory()); String path = pathValue.getString(); boolean absolute; try { Path p = sessionContext.getQPath(path); absolute = p.isAbsolute(); } catch (RepositoryException e) { throw new ValueFormatException("Property value cannot be converted to a PATH"); } try { return (absolute) ? getSession().getProperty(path) : getParent().getProperty(path); } catch (PathNotFoundException e) { throw new ItemNotFoundException(path); } } /** Wrapper around {@link #getValue()} */ public BigDecimal getDecimal() throws RepositoryException { return getValue().getDecimal(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(BigDecimal value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #getValue()} */ public Binary getBinary() throws RepositoryException { return getValue().getBinary(); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Binary value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Calendar value) throws RepositoryException { if (value != null) { try { setValue(getSession().getValueFactory().createValue(value)); } catch (IllegalArgumentException e) { throw new ValueFormatException( "Value is not an ISO8601 date: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(double value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(InputStream value) throws RepositoryException { if (value != null) { Binary binary = getValueFactory().createBinary(value); try { setValue(getValueFactory().createValue(binary)); } finally { binary.dispose(); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(String value) throws RepositoryException { if (value != null) { setValue(getValueFactory().createValue(value)); } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value[])} */ public void setValue(String[] strings) throws RepositoryException { if (strings != null) { setValue(getValues(strings, STRING)); } else { setValue((Value[]) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(boolean value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } /** Wrapper around {@link #setValue(Value)} */ public void setValue(Node value) throws RepositoryException { if (value != null) { try { setValue(getValueFactory().createValue(value)); } catch (UnsupportedRepositoryOperationException e) { throw new ValueFormatException( "Node is not referenceable: " + value, e); } } else { setValue((Value) null); } } /** Wrapper around {@link #setValue(Value)} */ public void setValue(long value) throws RepositoryException { setValue(getValueFactory().createValue(value)); } public synchronized void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(false); // check type according to definition of this property final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { if (value != null) { reqType = value.getType(); } else { reqType = STRING; } } if (value == null) { internalSetValue(null, reqType); return; } InternalValue internalValue; if (reqType != value.getType()) { // type conversion required Value targetVal = ValueHelper.convert( value, reqType, getSession().getValueFactory()); internalValue = InternalValue.create( targetVal, sessionContext, sessionContext.getDataStore()); } else { // no type conversion required internalValue = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } internalSetValue(new InternalValue[]{internalValue}, reqType); } /** * {@inheritDoc} */ public void setValue(Value[] values) throws RepositoryException { setValue(values, UNDEFINED); } /** * Sets the values of this property. * * @param values property values (possibly null) * @param valueType default value type if not set in the node type, * may be {@link PropertyType#UNDEFINED} * @throws RepositoryException if the property values could not be set */ public void setValue(Value[] values, int valueType) throws RepositoryException { // check state of this instance sanityCheck(); // check pre-conditions for setting property value checkSetValue(true); if (values != null) { // check type of values int firstValueType = UNDEFINED; for (Value value : values) { if (value != null) { if (firstValueType == UNDEFINED) { firstValueType = value.getType(); } else if (firstValueType != value.getType()) { throw new ValueFormatException( "inhomogeneous type of values"); } } } } final PropertyDefinition definition = data.getPropertyDefinition(); int reqType = definition.getRequiredType(); if (reqType == UNDEFINED) { reqType = valueType; // use the given type as property type } InternalValue[] internalValues = null; // convert to internal values of correct type if (values != null) { internalValues = new InternalValue[values.length]; // check type of values for (int i = 0; i < values.length; i++) { Value value = values[i]; if (value != null) { if (reqType == UNDEFINED) { // Use the type of the fist value as the type reqType = value.getType(); } if (reqType != value.getType()) { value = ValueHelper.convert( value, reqType, getSession().getValueFactory()); } internalValues[i] = InternalValue.create( value, sessionContext, sessionContext.getDataStore()); } else { internalValues[i] = null; } } } internalSetValue(internalValues, reqType); } /** * {@inheritDoc} */ public long getLength() throws RepositoryException { return getLength(internalGetValue()); } /** * {@inheritDoc} */ public long[] getLengths() throws RepositoryException { InternalValue[] values = internalGetValues(); long[] lengths = new long[values.length]; for (int i = 0; i < values.length; i++) { lengths[i] = getLength(values[i]); } return lengths; } /** * {@inheritDoc} */ public PropertyDefinition getDefinition() throws RepositoryException { // check state of this instance sanityCheck(); return data.getPropertyDefinition(); } /** * {@inheritDoc} */ public int getType() throws RepositoryException { return getPropertyState().getType(); } /** * {@inheritDoc} */ public boolean isMultiple() throws RepositoryException { // check state of this instance sanityCheck(); return getPropertyState().isMultiValued(); } //-----------------------------------------------------------------< Item > /** * {@inheritDoc} */ @Override public boolean isNode() { return false; } /** * {@inheritDoc} */ @Override public String getName() throws RepositoryException { // check state of this instance sanityCheck(); return sessionContext.getJCRName(((PropertyId) id).getName()); } /** * {@inheritDoc} */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { // check state of this instance sanityCheck(); visitor.visit(this); } /** * {@inheritDoc} */ @Override public Node getParent() throws RepositoryException { return getParent(true); } //--------------------------------------------------------------< Object > /** * Return a string representation of this property for diagnostic purposes. * * @return "property /path/to/item" */ public String toString() { return "property " + super.toString(); } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; import org.apache.jackrabbit.core.session.SessionOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; /** * ProtectedItemModifier: An abstract helper class to allow classes * residing outside of the core package to modify and remove protected items. * The protected item definitions are required in order not to have security * relevant content being changed through common item operations but forcing * the usage of the corresponding APIs, which assert that implementation * specific constraints are not violated. */ public abstract class ProtectedItemModifier { private static final int DEFAULT_PERM_CHECK = -1; private final int permission; protected ProtectedItemModifier() { this(DEFAULT_PERM_CHECK); } protected ProtectedItemModifier(int permission) { Class extends ProtectedItemModifier> cl = getClass(); 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"); } this.permission = permission; } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName) throws RepositoryException { return addNode(parentImpl, name, ntName, null); } protected NodeImpl addNode(NodeImpl parentImpl, Name name, Name ntName, NodeId nodeId) throws RepositoryException { checkPermission(parentImpl, name, getPermission(true, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); NodeTypeImpl nodeType = parentImpl.sessionContext.getNodeTypeManager().getNodeType(ntName); org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl def = parentImpl.getApplicableChildNodeDefinition(name, ntName); // check for name collisions // TODO: improve. copied from NodeImpl NodeState thisState = parentImpl.getNodeState(); ChildNodeEntry cne = thisState.getChildNodeEntry(name, 1); if (cne != null) { // there's already a child node entry with that name; // check same-name sibling setting of new node if (!def.allowsSameNameSiblings()) { throw new ItemExistsException(); } // check same-name sibling setting of existing node NodeId newId = cne.getId(); NodeImpl n = (NodeImpl) parentImpl.sessionContext.getItemManager().getItem(newId); if (!n.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException(); } } return parentImpl.createChildNode(name, nodeType, nodeId); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value) throws RepositoryException { return setProperty(parentImpl, name, value, false); } protected Property setProperty(NodeImpl parentImpl, Name name, Value value, boolean ignorePermissions) throws RepositoryException { if (!ignorePermissions) { checkPermission(parentImpl, name, getPermission(false, false)); } // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue intVs = InternalValue.create(value, parentImpl.sessionContext); return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs); } protected Property setProperty(NodeImpl parentImpl, Name name, Value[] values, int type) throws RepositoryException { checkPermission(parentImpl, name, getPermission(false, false)); // validation: make sure Node is not locked or checked-in. parentImpl.checkSetProperty(); InternalValue[] intVs = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { intVs[i] = InternalValue.create(values[i], parentImpl.sessionContext); } return parentImpl.internalSetProperty(name, intVs, type); } protected void removeItem(ItemImpl itemImpl) throws RepositoryException { NodeImpl n; if (itemImpl.isNode()) { n = (NodeImpl) itemImpl; } else { n = (NodeImpl) itemImpl.getParent(); } checkPermission(itemImpl, getPermission(itemImpl.isNode(), true)); // validation: make sure Node is not locked or checked-in. n.checkSetProperty(); itemImpl.perform(new ItemRemoveOperation(itemImpl, false)); } protected void markModified(NodeImpl parentImpl) throws RepositoryException { parentImpl.getOrCreateTransientItemState(); } protected T performProtected(SessionImpl session, SessionOperation operation) throws RepositoryException { ItemValidator itemValidator = session.context.getItemValidator(); return itemValidator.performRelaxed(operation, ItemValidator.CHECK_CONSTRAINTS); } private void checkPermission(ItemImpl item, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) item.getSession(); AccessManager acMgr = sImpl.getAccessManager(); Path path = item.getPrimaryPath(); acMgr.checkPermission(path, perm); } } private void checkPermission(NodeImpl node, Name childName, int perm) throws RepositoryException { if (perm > Permission.NONE) { SessionImpl sImpl = (SessionImpl) node.getSession(); AccessManager acMgr = sImpl.getAccessManager(); boolean isGranted = acMgr.isGranted(node.getPrimaryPath(), childName, perm); if (!isGranted) { throw new AccessDeniedException("Permission denied."); } } } private int getPermission(boolean isNode, boolean isRemove) { if (permission < Permission.NONE) { if (isNode) { return (isRemove) ? Permission.REMOVE_NODE : Permission.ADD_NODE; } else { return (isRemove) ? Permission.REMOVE_PROPERTY : Permission.SET_PROPERTY; } } else { return permission; } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT; import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS; import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD; import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK; import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionWriteOperation; import org.apache.jackrabbit.core.state.ChildNodeEntry; 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.SessionItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl; import org.apache.jackrabbit.value.ValueHelper; /** * Session operation for removing a mixin type from a node. */ class RemoveMixinOperation implements SessionWriteOperation { private final NodeImpl node; private final Name mixinName; public RemoveMixinOperation(NodeImpl node, Name mixinName) { this.node = node; this.mixinName = mixinName; } public Object perform(SessionContext context) throws RepositoryException { SessionImpl session = context.getSessionImpl(); ItemManager itemMgr = context.getItemManager(); SessionItemStateManager stateMgr = context.getItemStateManager(); context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, Permission.NODE_TYPE_MNGMT); // check if mixin is assigned NodeState state = node.getNodeState(); if (!state.getMixinTypeNames().contains(mixinName)) { throw new NoSuchNodeTypeException( "Mixin " + context.getJCRName(mixinName) + " not included in " + node); } NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeRegistry ntReg = context.getNodeTypeRegistry(); // build effective node type of remaining mixin's & primary type Set remainingMixins = new HashSet(state.getMixinTypeNames()); // remove name of target mixin remainingMixins.remove(mixinName); EffectiveNodeType entResulting; try { // build effective node type representing primary type // including remaining mixin's entResulting = ntReg.getEffectiveNodeType( state.getNodeTypeName(), remainingMixins); } catch (NodeTypeConflictException e) { throw new ConstraintViolationException(e.getMessage(), e); } // mix:referenceable needs special handling because it has // special semantics: // it can only be removed if there no more references to this node NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (isReferenceable(mixin) && !entResulting.includesNodeType(MIX_REFERENCEABLE)) { if (node.getReferences().hasNext()) { throw new ConstraintViolationException( mixinName + " can not be removed:" + " the node is being referenced through at least" + " one property of type REFERENCE"); } } // mix:lockable: the mixin cannot be removed if the node is // currently locked even if the editing session is the lock holder. if ((NameConstants.MIX_LOCKABLE.equals(mixinName) || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE)) && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE) && node.isLocked()) { throw new ConstraintViolationException( mixinName + " can not be removed: the node is locked."); } NodeState thisState = (NodeState) node.getOrCreateTransientItemState(); // collect information about properties and nodes which require further // action as a result of the mixin removal; we need to do this *before* // actually changing the assigned mixin types, otherwise we wouldn't // be able to retrieve the current definition of an item. Map affectedProps = new HashMap(); Map affectedNodes = new HashMap(); try { Set names = thisState.getPropertyNames(); for (Name propName : names) { PropertyId propId = new PropertyId(thisState.getNodeId(), propName); PropertyState propState = (PropertyState) stateMgr.getItemState(propId); PropertyDefinition oldDef = itemMgr.getDefinition(propState); // check if property has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this property affectedProps.put(propId, oldDef); } } List entries = thisState.getChildNodeEntries(); for (ChildNodeEntry entry : entries) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeDefinition oldDef = itemMgr.getDefinition(nodeState); // check if node has been defined by mixin type // (or one of its supertypes) NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType(); if (!entResulting.includesNodeType(declaringNT.getQName())) { // the resulting effective node type doesn't include the // node type that declared this child node affectedNodes.put(entry, oldDef); } } } catch (ItemStateException e) { throw new RepositoryException( "Failed to determine effect of removing mixin " + context.getJCRName(mixinName), e); } // modify the state of this node thisState.setMixinTypeNames(remainingMixins); // set jcr:mixinTypes property node.setMixinTypesProperty(remainingMixins); // process affected nodes & properties: // 1. try to redefine item based on the resulting // new effective node type (see JCR-2130) // 2. remove item if 1. fails boolean success = false; try { for (Map.Entry entry : affectedProps.entrySet()) { PropertyId id = entry.getKey(); PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id); PropertyDefinition oldDef = entry.getValue(); if (oldDef.isProtected()) { // remove 'orphaned' protected properties immediately node.removeChildProperty(id.getName()); continue; } // try to find new applicable definition first and // redefine property if possible (JCR-2130) try { PropertyDefinitionImpl newDef = node.getApplicablePropertyDefinition( id.getName(), prop.getType(), oldDef.isMultiple(), false); if (newDef.getRequiredType() != PropertyType.UNDEFINED && newDef.getRequiredType() != prop.getType()) { // value conversion required if (oldDef.isMultiple()) { // convert value Value[] values = ValueHelper.convert( prop.getValues(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(values); } else { // convert value Value value = ValueHelper.convert( prop.getValue(), newDef.getRequiredType(), session.getValueFactory()); // redefine property prop.onRedefine(newDef.unwrap()); // set converted values prop.setValue(value); } } else { // redefine property prop.onRedefine(newDef.unwrap()); } } catch (ValueFormatException vfe) { // value conversion failed, remove it node.removeChildProperty(id.getName()); } catch (ConstraintViolationException cve) { // no suitable definition found for this property, // remove it node.removeChildProperty(id.getName()); } } for (ChildNodeEntry entry : affectedNodes.keySet()) { NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId()); NodeImpl childNode = (NodeImpl) itemMgr.getItem(entry.getId()); NodeDefinition oldDef = affectedNodes.get(entry); if (oldDef.isProtected()) { // remove 'orphaned' protected child node immediately node.removeChildNode(entry.getId()); continue; } // try to find new applicable definition first and // redefine node if possible (JCR-2130) try { NodeDefinitionImpl newDef = node.getApplicableChildNodeDefinition( entry.getName(), nodeState.getNodeTypeName()); // redefine node childNode.onRedefine(newDef.unwrap()); } catch (ConstraintViolationException cve) { // no suitable definition found for this child node, // remove it node.removeChildNode(entry.getId()); } } success = true; } catch (ItemStateException e) { throw new RepositoryException( "Failed to clean up child items defined by removed mixin " + context.getJCRName(mixinName), e); } finally { if (!success) { // TODO JCR-1914: revert any changes made so far } } return this; } private boolean isReferenceable(NodeTypeImpl mixin) { return MIX_REFERENCEABLE.equals(mixinName) || mixin.isDerivedFrom(MIX_REFERENCEABLE); } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.removeMixin(" + mixinName + ")"; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.apache.jackrabbit.core.RepositoryImpl.SYSTEM_ROOT_NODE_ID; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ISCHECKEDOUT; import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESSORS; 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; import java.util.Set; import java.util.TimeZone; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.state.ChangeLog; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.version.InconsistentVersioningState; import org.apache.jackrabbit.core.version.InternalVersion; import org.apache.jackrabbit.core.version.InternalVersionHistory; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; import org.apache.jackrabbit.core.version.VersionHistoryInfo; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for checking for and optionally fixing consistency issues in a * repository. Currently this class only contains a simple versioning * recovery feature for * JCR-2551. */ class RepositoryChecker { /** * Logger instance. */ private static final Logger log = LoggerFactory.getLogger(RepositoryChecker.class); private final PersistenceManager workspace; private final ChangeLog workspaceChanges; private final ChangeLog vworkspaceChanges; private final InternalVersionManagerImpl versionManager; // 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(); this.vworkspaceChanges = new ChangeLog(); this.versionManager = versionManager; } 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())) { internalCheck(child.getId(), recurse, fixImmediately); } } } } catch (ItemStateException e) { throw new RepositoryException("Unable to access node " + id, e); } } private void fix(PersistenceManager pm, ChangeLog changes, String store, boolean verbose) throws RepositoryException { if (changes.hasUpdates()) { 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)"; log.error(message, e); throw new RepositoryException(message, e); } } else { if (verbose) { log.info("No " + store + " inconsistencies found"); } } } public void fix() throws RepositoryException { internalFix(true); } private void internalFix(boolean verbose) throws RepositoryException { fix(workspace, workspaceChanges, "workspace", verbose); fix(versionManager.getPersistenceManager(), vworkspaceChanges, "versioning workspace", verbose); dirtyNodes = 0; } private void checkVersionHistory(NodeState node) { String message = null; NodeId nid = node.getNodeId(); boolean isVersioned = node.hasPropertyName(JCR_VERSIONHISTORY); NodeId vhid = null; try { String type = isVersioned ? "in-use" : "candidate"; log.debug("Checking " + type + " version history of node {}", nid); String intro = "Removing references to an inconsistent " + type + " version history of node " + nid; message = intro + " (getting the VersionInfo)"; VersionHistoryInfo vhi = versionManager.getVersionHistoryInfoForNode(node); if (vhi != null) { // get the version history's node ID as early as possible // so we can attempt a fixup even when the next call fails vhid = vhi.getVersionHistoryId(); } message = intro + " (getting the InternalVersionHistory)"; InternalVersionHistory vh = null; try { vh = versionManager.getVersionHistoryOfNode(nid); } catch (ItemNotFoundException ex) { // it's ok if we get here if the node didn't claim to be versioned if (isVersioned) { throw ex; } } if (vh == null) { if (isVersioned) { message = intro + "getVersionHistoryOfNode returned null"; throw new InconsistentVersioningState(message); } } else { vhid = vh.getId(); // additional checks, see JCR-3101 message = intro + " (getting the version names failed)"; Name[] versionNames = vh.getVersionNames(); boolean seenRoot = false; for (Name versionName : versionNames) { seenRoot |= JCR_ROOTVERSION.equals(versionName); log.debug("Checking version history of node {}, version {}", nid, versionName); message = intro + " (getting version " + versionName + " failed)"; InternalVersion v = vh.getVersion(versionName); message = intro + "(frozen node of root version " + v.getId() + " missing)"; if (null == v.getFrozenNode()) { throw new InconsistentVersioningState(message); } } if (!seenRoot) { message = intro + " (root version is missing)"; throw new InconsistentVersioningState(message); } } } catch (InconsistentVersioningState e) { log.info(message, e); NodeId nvhid = e.getVersionHistoryNodeId(); if (nvhid != null) { if (vhid != null && !nvhid.equals(vhid)) { log.error("vhrid returned with InconsistentVersioningState does not match the id we already had: " + vhid + " vs " + nvhid); } vhid = nvhid; } removeVersionHistoryReferences(node, vhid); } catch (Exception e) { log.info(message, e); removeVersionHistoryReferences(node, vhid); } } // 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 mixins = new HashSet(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); } removeProperty(modified, JCR_VERSIONHISTORY); removeProperty(modified, JCR_BASEVERSION); removeProperty(modified, JCR_PREDECESSORS); 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(TimeZone.getTimeZone("UTC")); String appendme = String.format(" (disconnected by RepositoryChecker on %04d%02d%02dT%02d%02d%02dZ)", now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); modifiedParent.renameChildNodeEntry(vhid, nf.create(vhrname.getNamespaceURI(), vhrname.getLocalName() + appendme)); vworkspaceChanges.modified(modifiedParent); } else { log.info("child node entry " + vhrname + " for version history not found inside parent folder."); } } catch (Exception ex) { log.error("while trying to rename the version history", ex); } } } private void removeProperty(NodeState node, Name name) { if (node.hasPropertyName(name)) { node.removePropertyName(name); try { workspaceChanges.deleted(workspace.load( new PropertyId(node.getNodeId(), name))); } catch (ItemStateException ignoe) { } } } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.NodeIdFactory; 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.stats.RepositoryStatisticsImpl; import org.apache.jackrabbit.core.stats.StatManager; import org.apache.jackrabbit.core.version.InternalVersionManagerImpl; /** * Internal component context of a Jackrabbit content repository. * A repository context consists of the internal repository-level * components and resources like the namespace and node type * registries. Access to these resources is available only to objects * with a reference to the context object. */ public class RepositoryContext { /** * The repository instance to which this context is associated. */ private final RepositoryImpl repository; /** * The namespace registry of this repository. */ private NamespaceRegistryImpl namespaceRegistry; /** * The node type registry of this repository. */ private NodeTypeRegistry nodeTypeRegistry; /** * The privilege registry for this repository. */ private PrivilegeRegistry privilegeRegistry; /** * The internal version manager of this repository. */ private InternalVersionManagerImpl internalVersionManager; /** * The root node identifier of this repository. */ private NodeId rootNodeId; /** * The repository file system. */ private FileSystem fileSystem; /** * The data store of this repository, or null. */ private DataStore dataStore; /** * The cluster node instance of this repository, or null. */ private ClusterNode clusterNode; /** * Workspace manager of this repository. */ private WorkspaceManager workspaceManager; /** * Security manager of this repository; */ private JackrabbitSecurityManager securityManager; /** * Item state cache factory of this repository. */ private ItemStateCacheFactory itemStateCacheFactory; private NodeIdFactory nodeIdFactory; /** * Thread pool of this repository. */ private final ScheduledExecutorService executor = new JackrabbitThreadPool(); /** * Repository statistics collector. */ private final RepositoryStatisticsImpl statistics; /** * The Statistics manager, handles statistics */ private StatManager statManager; /** * flag to indicate if GC is running */ private volatile boolean gcRunning; /** * Creates a component context for the given repository. * * @param repository repository instance */ RepositoryContext(RepositoryImpl repository) { assert repository != null; this.repository = repository; this.statistics = new RepositoryStatisticsImpl(executor); this.statManager = new StatManager(); } /** * Starts a repository with the given configuration and returns * the internal component context of the started repository. * * @since Apache Jackrabbit 2.3.1 * @param config repository configuration * @return component context of the repository * @throws RepositoryException if the repository could not be started */ public static RepositoryContext create(RepositoryConfig config) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(config); return repository.getRepositoryContext(); } /** * Starts a repository in the given directory and returns the * internal component context of the started repository. If needed, * the directory is created and a default repository configuration * is installed inside it. * * @since Apache Jackrabbit 2.3.1 * @see RepositoryConfig#install(File) * @param dir repository directory * @return component context of the repository * @throws RepositoryException if the repository could not be started * @throws IOException if the directory could not be initialized */ public static RepositoryContext install(File dir) throws RepositoryException, IOException { return create(RepositoryConfig.install(dir)); } public RepositoryConfig getRepositoryConfig() { return repository.getConfig(); } /** * Returns the repository instance to which this context is associated. * * @return repository instance */ public RepositoryImpl getRepository() { return repository; } /** * Returns the thread pool of this repository. * * @return repository thread pool */ public ScheduledExecutorService getExecutor() { return executor; } /** * Returns the namespace registry of this repository. * * @return namespace registry */ public NamespaceRegistryImpl getNamespaceRegistry() { assert namespaceRegistry != null; return namespaceRegistry; } /** * Sets the namespace registry of this repository. * * @param namespaceRegistry namespace registry */ void setNamespaceRegistry(NamespaceRegistryImpl namespaceRegistry) { assert namespaceRegistry != null; this.namespaceRegistry = namespaceRegistry; } /** * Returns the namespace registry of this repository. * * @return node type registry */ public NodeTypeRegistry getNodeTypeRegistry() { assert nodeTypeRegistry != null; return nodeTypeRegistry; } /** * Sets the node type registry of this repository. * * @param nodeTypeRegistry node type registry */ void setNodeTypeRegistry(NodeTypeRegistry nodeTypeRegistry) { assert nodeTypeRegistry != null; this.nodeTypeRegistry = nodeTypeRegistry; } /** * Returns the privilege registry of this repository. * * @return the privilege registry of this repository. */ public PrivilegeRegistry getPrivilegeRegistry() { return privilegeRegistry; } /** * Sets the privilege registry of this repository. * * @param privilegeRegistry */ void setPrivilegeRegistry(PrivilegeRegistry privilegeRegistry) { assert privilegeRegistry != null; this.privilegeRegistry = privilegeRegistry; } /** * Returns the internal version manager of this repository. * * @return internal version manager */ public InternalVersionManagerImpl getInternalVersionManager() { return internalVersionManager; } /** * Sets the internal version manager of this repository. * * @param internalVersionManager internal version manager */ void setInternalVersionManager( InternalVersionManagerImpl internalVersionManager) { assert internalVersionManager != null; this.internalVersionManager = internalVersionManager; } /** * Returns the root node identifier of this repository. * * @return root node identifier */ public NodeId getRootNodeId() { assert rootNodeId != null; return rootNodeId; } /** * Sets the root node identifier of this repository. * * @param rootNodeId root node identifier */ void setRootNodeId(NodeId rootNodeId) { assert rootNodeId != null; this.rootNodeId = rootNodeId; } /** * Returns the repository file system. * * @return repository file system */ public FileSystem getFileSystem() { assert fileSystem != null; return fileSystem; } /** * Sets the repository file system. * * @param fileSystem repository file system */ void setFileSystem(FileSystem fileSystem) { assert fileSystem != null; this.fileSystem = fileSystem; } /** * Returns the data store of this repository, or null * if a data store is not configured. * * @return data store, or null */ public DataStore getDataStore() { return dataStore; } /** * Sets the data store of this repository. * * @param dataStore data store */ void setDataStore(DataStore dataStore) { assert dataStore != null; this.dataStore = dataStore; } /** * Returns the cluster node instance of this repository, or * null if clustering is not enabled. * * @return cluster node */ public ClusterNode getClusterNode() { return clusterNode; } /** * Sets the cluster node instance of this repository. * * @param clusterNode cluster node */ void setClusterNode(ClusterNode clusterNode) { assert clusterNode != null; this.clusterNode = clusterNode; } /** * Returns the workspace manager of this repository. * * @return workspace manager */ public WorkspaceManager getWorkspaceManager() { assert workspaceManager != null; return workspaceManager; } /** * Sets the workspace manager of this repository. * * @param workspaceManager workspace manager */ void setWorkspaceManager(WorkspaceManager workspaceManager) { assert workspaceManager != null; this.workspaceManager = workspaceManager; } /** * 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 null. * @return The {@link WorkspaceInfo} for the named workspace. This will * never be null. * @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 */ public JackrabbitSecurityManager getSecurityManager() { assert securityManager != null; return securityManager; } /** * Sets the security manager of this repository. * * @param securityManager security manager */ void setSecurityManager(JackrabbitSecurityManager securityManager) { assert securityManager != null; this.securityManager = securityManager; } /** * Returns the item state cache factory of this repository. * * @return item state cache factory */ public ItemStateCacheFactory getItemStateCacheFactory() { assert itemStateCacheFactory != null; return itemStateCacheFactory; } /** * Sets the item state cache factory of this repository. * * @param itemStateCacheFactory item state cache factory */ void setItemStateCacheFactory(ItemStateCacheFactory itemStateCacheFactory) { assert itemStateCacheFactory != null; this.itemStateCacheFactory = itemStateCacheFactory; } public void setNodeIdFactory(NodeIdFactory nodeIdFactory) { this.nodeIdFactory = nodeIdFactory; } public NodeIdFactory getNodeIdFactory() { return nodeIdFactory; } /** * Returns the repository statistics collector. * * @return repository statistics collector */ public RepositoryStatisticsImpl getRepositoryStatistics() { return statistics; } /** * @return the statistics manager object */ public StatManager getStatManager() { return statManager; } /** * * @return gcRunning status */ public synchronized boolean isGcRunning() { return gcRunning; } /** * set gcRunnign status * @param gcRunning */ public synchronized void setGcRunning(boolean gcRunning) { this.gcRunning = gcRunning; } } ================================================ FILE: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryCopier.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; import org.apache.jackrabbit.core.persistence.PersistenceCopier; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool for backing up or migrating the entire contents (workspaces, * version histories, namespaces, node types, etc.) of a repository to * a new repository. The target repository (if it exists) is overwritten. * * No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. * * The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. * * The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection
NodeTypeInstanceHandler
PropertyImpl
setValue()
{@link Property#setValue(String)}
{@link Property#setValue(String[])}
ProtectedItemModifier
* No cluster journal records are written in the target repository. If the * target repository is clustered, it should be the only node in the cluster. *
* The target repository needs to be fully reindexed after the copy operation. * The static copy() methods will remove the target search index folders from * their default locations to trigger automatic reindexing when the repository * is next started. * * @since Apache Jackrabbit 1.6 */ public class RepositoryCopier { /** * Logger instance */ private static final Logger logger = LoggerFactory.getLogger(RepositoryCopier.class); /** * Source repository context. */ private final RepositoryContext source; /** * Target repository context. */ private final RepositoryContext target; /** * Copies the contents of the repository in the given source directory * to a repository in the given target directory. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(File source, File target) throws RepositoryException, IOException { copy(RepositoryConfig.create(source), RepositoryConfig.install(target)); } /** * Copies the contents of the repository with the given configuration * to a repository in the given target directory. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryConfig source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the source repository with the given * configuration to a target repository with the given configuration. * * @param source source repository configuration * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryConfig source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(source); try { copy(repository, target); } finally { repository.shutdown(); } } /** * Copies the contents of the given source repository to a repository in * the given target directory. *
* The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails * @throws IOException if the target repository can not be initialized */ public static void copy(RepositoryImpl source, File target) throws RepositoryException, IOException { copy(source, RepositoryConfig.install(target)); } /** * Copies the contents of the given source repository to a target * repository with the given configuration. *
* The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. * * @param source source repository directory * @param target target repository directory * @throws RepositoryException if the copy operation fails */ public static void copy(RepositoryImpl source, RepositoryConfig target) throws RepositoryException { RepositoryImpl repository = RepositoryImpl.create(target); try { new RepositoryCopier(source, repository).copy(); } finally { repository.shutdown(); } // Remove index directories to force re-indexing on next startup // TODO: There should be a cleaner way to do this File targetDir = new File(target.getHomeDir()); File repoDir = new File(targetDir, "repository"); FileUtils.deleteQuietly(new File(repoDir, "index")); File[] workspaces = new File(targetDir, "workspaces").listFiles(); if (workspaces != null) { for (File workspace : workspaces) { FileUtils.deleteQuietly(new File(workspace, "index")); } } } /** * Creates a tool for copying the full contents of the source repository * to the given target repository. Any existing content in the target * repository will be overwritten. * * @param source source repository * @param target target repository */ public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) { // TODO: It would be better if we were given the RepositoryContext // instances directly. Perhaps we should use something like // RepositoryImpl.getRepositoryCopier(RepositoryImpl target) // instead of this public constructor to achieve that. this.source = source.getRepositoryContext(); this.target = target.getRepositoryContext(); } /** * Copies the full content from the source to the target repository. *
* The source repository must not be modified while * the copy operation is running to avoid an inconsistent copy. *
* This method leaves the search indexes of the target repository in * an * Note that both the source and the target repository must be closed * during the copy operation as this method requires exclusive access * to the repositories. * * @throws RepositoryException if the copy operation fails */ public void copy() throws RepositoryException { logger.info( "Copying repository content from {} to {}", source.getRepository().repConfig.getHomeDir(), target.getRepository().repConfig.getHomeDir()); try { copyNamespaces(); copyNodeTypes(); copyVersionStore(); copyWorkspaces(); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } private void copyNamespaces() throws RepositoryException { NamespaceRegistry sourceRegistry = source.getNamespaceRegistry(); NamespaceRegistry targetRegistry = target.getNamespaceRegistry(); logger.info("Copying registered namespaces"); Collection existing = Arrays.asList(targetRegistry.getURIs()); for (String uri : sourceRegistry.getURIs()) { if (!existing.contains(uri)) { // TODO: what if the prefix is already taken? targetRegistry.registerNamespace( sourceRegistry.getPrefix(uri), uri); } } } private void copyNodeTypes() throws RepositoryException { NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry(); NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry(); logger.info("Copying registered node types"); Collection existing = Arrays.asList(targetRegistry.getRegisteredNodeTypes()); Collection