+ * This JCR repository must be configured with the ability to connect to a repository via a supplied
+ * {@link RepositoryConnectionFactory repository connection factory} and repository source name. An {@link ExecutionContext
+ * execution context} must also be supplied to enable working with the underlying ModeShape graph implementation to which this JCR
+ * implementation delegates.
+ *
+ *
+ * This should be changed to 'false' only in advanced situations, and never for production. Note that this also disables query
+ * execution.
+ *
+ */
+ static final boolean WORKSPACES_SHARE_SYSTEM_BRANCH = true;
+
+ /**
+ * The user name for anonymous sessions
+ *
+ * @see Option#ANONYMOUS_USER_ROLES
+ */
+ static final String ANONYMOUS_USER_NAME = "";
+ private static final Logger LOGGER = Logger.getLogger(JcrRepository.class);
+ private static Properties bundleProperties = null;
+
+ /**
+ * The available options for the {@code JcrRepository}.
+ */
+ public enum Option {
+
+ /**
+ * Flag that defines whether or not the node types should be exposed as content under the "{@code
+ * /jcr:system/jcr:nodeTypes}" node. Value is either "true
" or "false
" (default).
+ *
+ * @see DefaultOption#PROJECT_NODE_TYPES
+ */
+ PROJECT_NODE_TYPES,
+ /**
+ * The {@link Configuration#getAppConfigurationEntry(String) JAAS application configuration name} that specifies which
+ * login modules should be used to validate credentials.
+ */
+ JAAS_LOGIN_CONFIG_NAME,
+
+ /**
+ * The name of the source (and optionally the workspace in the source) where the "/jcr:system" branch should be stored.
+ * The format is "name of workspace@name of source
", or simply "name of source
" if the default
+ * workspace is to be used. If this option is not used, a transient in-memory source will be used.
+ *
+ * Note that all leading and trailing whitespace is removed for both the source name and workspace name. Thus, a value of
+ * "@
" implies a zero-length workspace name and zero-length source name.
+ *
+ *
+ * Also, any use of the '@' character in source and workspace names must be escaped with a preceding backslash.
+ *
+ */
+ SYSTEM_SOURCE_NAME,
+
+ /**
+ * The depth of the subgraphs that should be loaded from the connectors during normal read operations. The default value
+ * is 1.
+ */
+ READ_DEPTH,
+
+ /**
+ * The depth of the subgraphs that should be loaded from the connectors during indexing operations. The default value is
+ * 10.
+ */
+ INDEX_READ_DEPTH,
+
+ /**
+ * A comma-delimited list of default roles provided for anonymous access. A null or empty value for this option means that
+ * anonymous access is disabled.
+ */
+ ANONYMOUS_USER_ROLES,
+
+ /**
+ * The query system represents node types as tables that can be queried, but there are two ways to define the columns for
+ * each of those tables. One approach is that each table only has columns representing the (single-valued) property
+ * definitions explicitly defined by the node type. The other approach also adds columns for each of the (single-valued)
+ * property definitions inherited by the node type from all of the {@link javax.jcr.nodetype.NodeType#getSupertypes()}.
+ *
+ * The default value is 'true'.
+ *
+ */
+ TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES,
+
+ /**
+ * A boolean flag that specifies whether this repository is expected to execute searches and queries. If client
+ * applications will never perform searches or queries, then maintaining the query indexes is an unncessary overhead, and
+ * can be disabled. Note that this is merely a hint, and that searches and queries might still work when this is set to
+ * 'false'.
+ *
+ * The default is 'true', meaning that clients can execute searches and queries.
+ *
+ */
+ QUERY_EXECUTION_ENABLED,
+
+ /**
+ * The system may maintain a set of indexes that improve the performance of searching and querying the content. These size
+ * of these indexes depend upon the size of the content being stored, and thus may consume a significant amount of space.
+ * This option defines a location on the file system where this repository may (if needed) store indexes so they don't
+ * consume large amounts of memory.
+ *
+ * If specified, the value must be a valid path to a writable directory on the file system. If the path specifies a
+ * non-existant location, the repository may attempt to create the missing directories. The path may be absolute or
+ * relative to the location where this VM was started. If the specified location is not a readable and writable directory
+ * (or cannot be created as such), then this will generate an exception when the repository is created.
+ *
+ *
+ * The default value is null, meaning the search indexes may not be stored on the local file system and, if needed, will
+ * be stored within memory.
+ *
+ */
+ QUERY_INDEX_DIRECTORY,
+
+ /**
+ * A boolean flag that specifies whether updates to the indexes (if used) should be made synchronously, meaning that a
+ * call to {@link Session#save()} will not return until the search indexes have been completely updated. The benefit of
+ * synchronous updates is that a search or query performed immediately after a save()
will operate upon
+ * content that was just changed. The downside is that the save()
operation will take longer.
+ *
+ * With asynchronous updates, however, the only work done during a save()
invocation is that required to
+ * persist the changes in the underlying repository source, while changes to the search indexes are made in a different
+ * thread that may not run immediately. In this case, there may be an indeterminate lag before searching or querying after
+ * a save()
will operate upon the changed content.
+ *
+ *
+ * The default is value 'false', meaning the updates are performed asynchronously .
+ *
+ */
+ QUERY_INDEXES_UPDATED_SYNCHRONOUSLY,
+
+ /**
+ * A boolean flag that specifies whether referential integrity checks should be performed upon {@link Session#save()}. By
+ * default, this option is enabled, meaning that referential integrity checks are performed to ensure that nodes
+ * referenced by other nodes cannot be removed.
+ *
+ * If no {@link PropertyType#REFERENCE} properties are used within your content, these referential integrity checks will
+ * never find referring nodes. In these cases, you may be able to improve performance by skipping these checks.
+ *
+ *
+ * The default value is 'true', meaning that these checks are performed.
+ *
+ */
+ PERFORM_REFERENTIAL_INTEGRITY_CHECKS;
+
+ /**
+ * Determine the option given the option name. This does more than {@link Option#valueOf(String)}, since this method first
+ * tries to match the supplied string to the option's {@link Option#name() name}, then the uppercase version of the
+ * supplied string to the option's name, and finally if the supplied string is a camel-case version of the name (e.g.,
+ * "projectNodeTypes").
+ *
+ * @param option the string version of the option's name
+ * @return the matching Option instance, or null if an option could not be matched using the supplied value
+ */
+ public static Option findOption( String option ) {
+ if (option == null) return null;
+ try {
+ return Option.valueOf(option);
+ } catch (IllegalArgumentException e) {
+ // Try an uppercased version ...
+ try {
+ return Option.valueOf(option.toUpperCase());
+ } catch (IllegalArgumentException e2) {
+ // Try a camel-case version ...
+ String underscored = Inflector.getInstance().underscore(option, '_');
+ if (underscored == null) {
+ throw e2;
+ }
+ return Option.valueOf(underscored.toUpperCase());
+ }
+ }
+ }
+ }
+
+ /**
+ * The default values for each of the {@link Option}.
+ */
+ public static class DefaultOption {
+ /**
+ * The default value for the {@link Option#PROJECT_NODE_TYPES} option is {@value} .
+ */
+ public static final String PROJECT_NODE_TYPES = Boolean.TRUE.toString();
+
+ /**
+ * The default value for the {@link Option#JAAS_LOGIN_CONFIG_NAME} option is {@value} .
+ */
+ public static final String JAAS_LOGIN_CONFIG_NAME = "modeshape-jcr";
+
+ /**
+ * The default value for the {@link Option#READ_DEPTH} option is {@value} .
+ */
+ public static final String READ_DEPTH = "1";
+
+ /**
+ * The default value for the {@link Option#INDEX_READ_DEPTH} option is {@value} .
+ */
+ public static final String INDEX_READ_DEPTH = "4";
+
+ /**
+ * The default value for the {@link Option#ANONYMOUS_USER_ROLES} option is {@value} .
+ */
+ public static final String ANONYMOUS_USER_ROLES = ModeShapeRoles.ADMIN;
+
+ /**
+ * The default value for the {@link Option#TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES} option is {@value} .
+ */
+ public static final String TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES = Boolean.TRUE.toString();
+
+ /**
+ * The default value for the {@link Option#QUERY_EXECUTION_ENABLED} option is {@value} .
+ */
+ public static final String QUERY_EXECUTION_ENABLED = Boolean.TRUE.toString();
+
+ /**
+ * The default value for the {@link Option#QUERY_INDEXES_UPDATED_SYNCHRONOUSLY} option is {@value} .
+ */
+ public static final String QUERY_INDEXES_UPDATED_SYNCHRONOUSLY = Boolean.TRUE.toString();
+
+ /**
+ * The default value for the {@link Option#QUERY_INDEX_DIRECTORY} option is {@value} .
+ */
+ public static final String QUERY_INDEX_DIRECTORY = null;
+
+ /**
+ * The default value for the {@link Option#PERFORM_REFERENTIAL_INTEGRITY_CHECKS} option is {@value} .
+ */
+ public static final String PERFORM_REFERENTIAL_INTEGRITY_CHECKS = Boolean.TRUE.toString();
+
+ }
+
+ /**
+ * The static unmodifiable map of default options, which are initialized in the static initializer.
+ */
+ protected static final Map DEFAULT_OPTIONS;
+
+ /**
+ * The set of supported query language string constants.
+ *
+ * @see javax.jcr.query.QueryManager#getSupportedQueryLanguages()
+ * @see javax.jcr.query.QueryManager#createQuery(String, String)
+ */
+ public static final class QueryLanguage {
+ /**
+ * The standard JCR 1.0 XPath query language.
+ */
+ @SuppressWarnings( "deprecation" )
+ public static final String XPATH = Query.XPATH;
+
+ /**
+ * The SQL dialect that is based upon an enhanced version of the JCR-SQL query language defined by the JCR 1.0.1
+ * specification.
+ */
+ @SuppressWarnings( "deprecation" )
+ public static final String JCR_SQL = Query.SQL;
+
+ /**
+ * The SQL dialect that is based upon an enhanced version of the JCR-SQL2 query language defined by the JCR 2.0
+ * specification.
+ */
+ public static final String JCR_SQL2 = Query.JCR_SQL2;
+
+ /**
+ * The enhanced Query Object Model language defined by the JCR 2.0 specification.
+ */
+ public static final String JCR_JQOM = Query.JCR_JQOM;
+ /**
+ * The full-text search language defined as part of the abstract query model, in Section 6.7.19 of the JCR 2.0
+ * specification.
+ */
+ public static final String SEARCH = FullTextSearchParser.LANGUAGE;
+ }
+
+ static {
+ // Initialize the unmodifiable map of default options ...
+ EnumMap defaults = new EnumMap (Option.class);
+ defaults.put(Option.PROJECT_NODE_TYPES, DefaultOption.PROJECT_NODE_TYPES);
+ defaults.put(Option.JAAS_LOGIN_CONFIG_NAME, DefaultOption.JAAS_LOGIN_CONFIG_NAME);
+ defaults.put(Option.READ_DEPTH, DefaultOption.READ_DEPTH);
+ defaults.put(Option.INDEX_READ_DEPTH, DefaultOption.INDEX_READ_DEPTH);
+ defaults.put(Option.ANONYMOUS_USER_ROLES, DefaultOption.ANONYMOUS_USER_ROLES);
+ defaults.put(Option.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES,
+ DefaultOption.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES);
+ defaults.put(Option.QUERY_EXECUTION_ENABLED, DefaultOption.QUERY_EXECUTION_ENABLED);
+ defaults.put(Option.QUERY_INDEXES_UPDATED_SYNCHRONOUSLY, DefaultOption.QUERY_INDEXES_UPDATED_SYNCHRONOUSLY);
+ defaults.put(Option.QUERY_INDEX_DIRECTORY, DefaultOption.QUERY_INDEX_DIRECTORY);
+ defaults.put(Option.PERFORM_REFERENTIAL_INTEGRITY_CHECKS, DefaultOption.PERFORM_REFERENTIAL_INTEGRITY_CHECKS);
+ DEFAULT_OPTIONS = Collections. unmodifiableMap(defaults);
+ }
+
+ private final String sourceName;
+ private final Map descriptors;
+ private final ExecutionContext executionContext;
+ private final RepositoryConnectionFactory connectionFactory;
+ private final RepositoryNodeTypeManager repositoryTypeManager;
+ private final RepositoryLockManager repositoryLockManager;
+ private final Map options;
+ private final String systemSourceName;
+ private final String systemWorkspaceName;
+ private final Projection systemSourceProjection;
+ private final FederatedRepositorySource federatedSource;
+ private final GraphNamespaceRegistry persistentRegistry;
+ private final RepositoryObservationManager repositoryObservationManager;
+ private final SecurityContext anonymousUserContext;
+ private final QueryParsers queryParsers;
+
+ // Until the federated connector supports queries, we have to use a search engine ...
+ private final RepositoryQueryManager queryManager;
+
+ // package-scoped to facilitate testing
+ final WeakHashMap activeSessions = new WeakHashMap();
+
+ /**
+ * Creates a JCR repository that uses the supplied {@link RepositoryConnectionFactory repository connection factory} to
+ * establish {@link Session sessions} to the underlying repository source upon {@link #login() login}.
+ *
+ * @param executionContext the execution context in which this repository is to operate
+ * @param connectionFactory the factory for repository connections
+ * @param repositorySourceName the name of the repository source (in the connection factory) that should be used
+ * @param repositoryObservable the repository library observable associated with this repository (never null
)
+ * @param repositorySourceCapabilities the capabilities of the repository source; may be null if the capabilities are not
+ * known
+ * @param descriptors the {@link #getDescriptorKeys() descriptors} for this repository; may be null
.
+ * @param options the optional {@link Option settings} for this repository; may be null
+ * @throws RepositoryException if there is a problem setting up this repository
+ * @throws IllegalArgumentException If executionContext
, connectionFactory
,
+ * repositorySourceName
, or repositoryObservable
is null
.
+ */
+ @SuppressWarnings( "deprecation" )
+ JcrRepository( ExecutionContext executionContext,
+ RepositoryConnectionFactory connectionFactory,
+ String repositorySourceName,
+ Observable repositoryObservable,
+ RepositorySourceCapabilities repositorySourceCapabilities,
+ Map descriptors,
+ Map options ) throws RepositoryException {
+ CheckArg.isNotNull(executionContext, "executionContext");
+ CheckArg.isNotNull(connectionFactory, "connectionFactory");
+ CheckArg.isNotNull(repositorySourceName, "repositorySourceName");
+ CheckArg.isNotNull(repositoryObservable, "repositoryObservable");
+
+ // Set up the options ...
+ if (options == null) {
+ this.options = DEFAULT_OPTIONS;
+ } else {
+ // Initialize with defaults, then add supplied options ...
+ EnumMap localOptions = new EnumMap (DEFAULT_OPTIONS);
+ localOptions.putAll(options);
+ this.options = Collections.unmodifiableMap(localOptions);
+ }
+
+ // Set up the system source ...
+ String systemSourceNameValue = this.options.get(Option.SYSTEM_SOURCE_NAME);
+ String systemSourceName = null;
+ String systemWorkspaceName = null;
+ RepositoryConnectionFactory connectionFactoryWithSystem = connectionFactory;
+ if (systemSourceNameValue != null) {
+ // Find an existing source with the given name containing the named workspace ...
+ try {
+ SourceWorkspacePair pair = new SourceWorkspacePair(systemSourceNameValue);
+ // Look for a source with the given name ...
+ RepositoryConnection conn = connectionFactory.createConnection(pair.getSourceName());
+ if (conn != null) {
+ // We found a source that we can use for the system ...
+ systemSourceName = pair.getSourceName();
+ if (pair.getWorkspaceName() != null) {
+ // There should be the named workspace ...
+ Graph temp = Graph.create(conn, executionContext);
+ temp.useWorkspace(pair.getWorkspaceName());
+ // found it ...
+ systemWorkspaceName = pair.getWorkspaceName();
+ }
+ } else {
+ I18n msg = JcrI18n.systemSourceNameOptionValueDoesNotReferenceExistingSource;
+ LOGGER.warn(msg, systemSourceNameValue, systemSourceName);
+ }
+ } catch (InvalidWorkspaceException e) {
+ // Bad workspace name ...
+ systemSourceName = null;
+ I18n msg = JcrI18n.systemSourceNameOptionValueDoesNotReferenceValidWorkspace;
+ LOGGER.warn(msg, systemSourceNameValue, systemSourceName);
+ } catch (IllegalArgumentException e) {
+ // Invalid format ...
+ systemSourceName = null;
+ I18n msg = JcrI18n.systemSourceNameOptionValueIsNotFormattedCorrectly;
+ LOGGER.warn(msg, systemSourceNameValue);
+ }
+ }
+ InMemoryRepositorySource transientSystemSource = null;
+ if (systemSourceName == null) {
+ // Create the in-memory repository source that we'll use for the "/jcr:system" branch in this repository.
+ // All workspaces will be set up with a federation connector that projects this system repository into
+ // "/jcr:system", and all other content is projected to the repositories actual source (and workspace).
+ // (The federation connector refers to this configuration as an "offset mirror".)
+ systemWorkspaceName = "jcr:system";
+ systemSourceName = "jcr:system source";
+ transientSystemSource = new InMemoryRepositorySource();
+ transientSystemSource.setName(systemSourceName);
+ transientSystemSource.setDefaultWorkspaceName(systemWorkspaceName);
+ connectionFactoryWithSystem = new DelegatingConnectionFactory(connectionFactory, transientSystemSource);
+ }
+
+ // Set up the query parsers, which we have to have even though queries might be disabled ...
+ this.queryParsers = new QueryParsers(new JcrSql2QueryParser(), new XPathQueryParser(), new FullTextSearchParser(),
+ new JcrSqlQueryParser(), new JcrQomQueryParser());
+ assert this.queryParsers.getParserFor(Query.XPATH) != null;
+ assert this.queryParsers.getParserFor(Query.SQL) != null;
+ assert this.queryParsers.getParserFor(Query.JCR_SQL2) != null;
+ assert this.queryParsers.getParserFor(Query.JCR_JQOM) != null;
+ assert this.queryParsers.getParserFor(QueryLanguage.SEARCH) != null;
+
+ this.systemWorkspaceName = systemWorkspaceName;
+ this.systemSourceName = systemSourceName;
+ this.connectionFactory = connectionFactoryWithSystem;
+ assert this.systemSourceName != null;
+ assert this.connectionFactory != null;
+ this.sourceName = repositorySourceName;
+
+ // Set up the "/jcr:system" branch ...
+ Graph systemGraph = Graph.create(this.systemSourceName, this.connectionFactory, executionContext);
+ systemGraph.useWorkspace(systemWorkspaceName);
+ initializeSystemContent(systemGraph);
+
+ // Create the namespace registry and corresponding execution context.
+ // Note that this persistent registry has direct access to the system workspace.
+ Name uriProperty = ModeShapeLexicon.URI;
+ PathFactory pathFactory = executionContext.getValueFactories().getPathFactory();
+ Path systemPath = pathFactory.create(JcrLexicon.SYSTEM);
+ final Path namespacesPath = pathFactory.create(systemPath, ModeShapeLexicon.NAMESPACES);
+ PropertyFactory propertyFactory = executionContext.getPropertyFactory();
+ Property namespaceType = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.NAMESPACE);
+
+ // Now create the registry implementation ...
+ this.persistentRegistry = new GraphNamespaceRegistry(systemGraph, namespacesPath, uriProperty, namespaceType);
+ this.executionContext = executionContext.with(persistentRegistry);
+
+ // Add the built-ins, ensuring we overwrite any badly-initialized values ...
+ for (Map.Entry builtIn : JcrNamespaceRegistry.STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX.entrySet()) {
+ this.persistentRegistry.register(builtIn.getKey(), builtIn.getValue());
+ }
+
+ // Set up the repository type manager ...
+ Path parentOfTypeNodes = null;
+
+ if (Boolean.valueOf(this.options.get(Option.PROJECT_NODE_TYPES))) {
+ parentOfTypeNodes = pathFactory.create(systemPath, JcrLexicon.NODE_TYPES);
+ }
+
+ try {
+ boolean includeInheritedProperties = Boolean.valueOf(this.options.get(Option.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES));
+ boolean includePseudoColumnInSelectStar = true;
+
+ // this.repositoryTypeManager = new RepositoryNodeTypeManager(this, includeInheritedProperties);
+ this.repositoryTypeManager = new RepositoryNodeTypeManager(this, parentOfTypeNodes, includeInheritedProperties,
+ includePseudoColumnInSelectStar);
+ CndNodeTypeReader nodeTypeReader = new CndNodeTypeReader(this.executionContext);
+ nodeTypeReader.readBuiltInTypes();
+ this.repositoryTypeManager.registerNodeTypes(nodeTypeReader);
+ } catch (RepositoryException re) {
+ throw new IllegalStateException("Could not load node type definition files", re);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Could not access node type definition files", ioe);
+ }
+ if (WORKSPACES_SHARE_SYSTEM_BRANCH) {
+
+ // Create the projection for the system repository ...
+ ProjectionParser projectionParser = ProjectionParser.getInstance();
+ String rule = "/jcr:system => /jcr:system";
+ Projection.Rule[] systemProjectionRules = projectionParser.rulesFromString(this.executionContext, rule);
+ this.systemSourceProjection = new Projection(systemSourceName, systemWorkspaceName, true, systemProjectionRules);
+
+ // Define the federated repository source. Use the same name as the repository, since this federated source
+ // will not be in the connection factory ...
+ this.federatedSource = new FederatedRepositorySource();
+ this.federatedSource.setName("JCR " + repositorySourceName);
+ this.federatedSource.initialize(new FederatedRepositoryContext(this.connectionFactory));
+
+ // Set up the workspaces corresponding to all those available in the source (except the system)
+ Graph graph = Graph.create(sourceName, connectionFactory, executionContext);
+ String defaultWorkspaceName = graph.getCurrentWorkspaceName();
+ for (String workspaceName : graph.getWorkspaces()) {
+ boolean isDefault = workspaceName.equals(defaultWorkspaceName);
+ addWorkspace(workspaceName, isDefault);
+ }
+ } else {
+ this.federatedSource = null;
+ this.systemSourceProjection = null;
+ }
+
+ if (descriptors == null) descriptors = new HashMap();
+ // Determine if it's possible to manage workspaces with the underlying source ...
+ if (repositorySourceCapabilities != null && repositorySourceCapabilities.supportsCreatingWorkspaces()) {
+ // Don't overwrite (so they workspace management can be disabled) ...
+ if (!descriptors.containsKey(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED)) {
+ descriptors.put(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED, Boolean.TRUE.toString());
+ }
+ } else {
+ // Not possible, so overwrite any value that might have been added ...
+ descriptors.put(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED, Boolean.FALSE.toString());
+ }
+
+ // Initialize required JCR descriptors.
+ this.descriptors = initializeDescriptors(executionContext.getValueFactories(), descriptors);
+
+ // If the repository is to support searching ...
+ if (Boolean.valueOf(this.options.get(Option.QUERY_EXECUTION_ENABLED)) && WORKSPACES_SHARE_SYSTEM_BRANCH) {
+ // Determine whether the federated source and original source support queries and searches ...
+ RepositorySourceCapabilities fedCapabilities = federatedSource != null ? federatedSource.getCapabilities() : null;
+ final boolean canQuerySource = repositorySourceCapabilities != null
+ && repositorySourceCapabilities.supportsSearches()
+ && repositorySourceCapabilities.supportsQueries();
+ final boolean canQueryFederated = fedCapabilities != null && fedCapabilities.supportsSearches()
+ && fedCapabilities.supportsQueries();
+
+ // We can query the federated source if it supports queries and searches
+ // AND the original source supports queries and searches ...
+ if (canQuerySource && canQueryFederated) {
+ this.queryManager = new PushDown(this.sourceName, this.executionContext, connectionFactory);
+ } else {
+ // Otherwise create a repository query manager that maintains its own search engine ...
+ String indexDirectory = this.options.get(Option.QUERY_INDEX_DIRECTORY);
+ boolean updateIndexesSynchronously = Boolean.valueOf(this.options.get(Option.QUERY_INDEXES_UPDATED_SYNCHRONOUSLY));
+ int maxDepthToRead = Integer.valueOf(this.options.get(Option.INDEX_READ_DEPTH));
+ this.queryManager = new RepositoryQueryManager.SelfContained(this.executionContext, this.sourceName,
+ connectionFactory, repositoryObservable,
+ repositoryTypeManager, indexDirectory,
+ updateIndexesSynchronously, maxDepthToRead);
+ }
+ } else {
+ this.queryManager = new RepositoryQueryManager.Disabled(this.sourceName);
+ }
+
+ // Initialize the observer, which receives events from all repository sources
+ this.repositoryObservationManager = new RepositoryObservationManager(repositoryObservable);
+ if (transientSystemSource != null) {
+ // The transient RepositorySource for the system content is not in the RepositoryLibrary, so we need to observe it ...
+ final Observer observer = this.repositoryObservationManager;
+ final ExecutionContext context = executionContext;
+ transientSystemSource.initialize(new RepositoryContext() {
+ @Override
+ public Observer getObserver() {
+ return observer;
+ }
+
+ @Override
+ public ExecutionContext getExecutionContext() {
+ return context;
+ }
+
+ @Override
+ public Subgraph getConfiguration( int depth ) {
+ return null; // not needed for the in-memory transient repository
+ }
+
+ @Override
+ public RepositoryConnectionFactory getRepositoryConnectionFactory() {
+ return null; // not needed for the in-memory transient repository
+ }
+ });
+ }
+
+ /*
+ * Set up the anonymous role, if appropriate
+ */
+ SecurityContext anonymousUserContext = null;
+ String rawAnonRoles = this.options.get(Option.ANONYMOUS_USER_ROLES);
+ if (rawAnonRoles != null) {
+ final Set roles = new HashSet();
+ for (String role : rawAnonRoles.split("\\s*,\\s*")) {
+ roles.add(role);
+ }
+ if (roles.size() > 0) {
+ anonymousUserContext = new SecurityContext() {
+
+ public String getUserName() {
+ return ANONYMOUS_USER_NAME;
+ }
+
+ public boolean hasRole( String roleName ) {
+ return roles.contains(roleName);
+ }
+
+ public void logout() {
+ }
+
+ };
+ }
+ }
+
+ this.anonymousUserContext = anonymousUserContext;
+
+ repositoryLockManager = new RepositoryLockManager(this);
+
+ // Create a system observer to update the namespace registry cache ...
+ final GraphNamespaceRegistry persistentRegistry = this.persistentRegistry;
+ final JcrSystemObserver namespaceObserver = new JcrSystemObserver() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.jcr.JcrSystemObserver#getObservedPath()
+ */
+ public Path getObservedPath() {
+ return namespacesPath;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.observe.Observer#notify(org.modeshape.graph.observe.Changes)
+ */
+ public void notify( Changes changes ) {
+ // These changes apply to anything at or below the namespaces path ...
+ persistentRegistry.refresh();
+ }
+ };
+
+ // Define the set of "/jcr:system" observers ...
+ // This observer picks up notification of changes to the system graph in a cluster. It's a NOP if there is no cluster.
+ repositoryObservationManager.register(new SystemChangeObserver(Arrays.asList(new JcrSystemObserver[] {
+ repositoryLockManager, namespaceObserver, repositoryTypeManager})));
+ }
+
+ protected void addWorkspace( String workspaceName,
+ boolean isDefault ) {
+ synchronized (this.federatedSource) {
+ if (this.federatedSource == null) return;
+ assert this.systemSourceProjection != null;
+ if (!this.federatedSource.hasWorkspace(workspaceName)) {
+ if (workspaceName.equals(systemWorkspaceName)) return;
+ // Add the workspace to the federated source ...
+ ProjectionParser projectionParser = ProjectionParser.getInstance();
+ Projection.Rule[] mirrorRules = projectionParser.rulesFromString(this.executionContext, "/ => /");
+ List projections = new ArrayList(2);
+ projections.add(new Projection(sourceName, workspaceName, false, mirrorRules));
+ projections.add(this.systemSourceProjection);
+ this.federatedSource.addWorkspace(workspaceName, projections, isDefault);
+ }
+ }
+ }
+
+ /**
+ * Create a new workspace with the supplied name.
+ *
+ * @param workspaceName the name of the workspace to be destroyed; may not be null
+ * @param clonedFromWorkspaceNamed the name of the workspace that is to be cloned, or null if the new workspace is to be empty
+ * @throws InvalidWorkspaceException if the workspace cannot be created because one already exists
+ * @throws UnsupportedRepositoryOperationException if this repository does not support workspace management
+ */
+ protected void createWorkspace( String workspaceName,
+ String clonedFromWorkspaceNamed )
+ throws InvalidWorkspaceException, UnsupportedRepositoryOperationException {
+ assert workspaceName != null;
+ if (!Boolean.parseBoolean(getDescriptor(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED))) {
+ throw new UnsupportedRepositoryOperationException();
+ }
+ if (workspaceName.equals(systemWorkspaceName)) {
+ // Cannot create a workspace that has the same name as the system workspace ...
+ String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(workspaceName, systemSourceName);
+ throw new InvalidWorkspaceException(msg);
+ }
+ // Create a graph to the underlying source ...
+ Graph graph = Graph.create(sourceName, connectionFactory, executionContext);
+ // Create the workspace (which will fail if workspaces cannot be created) ...
+ Workspace graphWorkspace = null;
+ if (clonedFromWorkspaceNamed != null) {
+ graphWorkspace = graph.createWorkspace().clonedFrom(clonedFromWorkspaceNamed).named(workspaceName);
+ } else {
+ graphWorkspace = graph.createWorkspace().named(workspaceName);
+ }
+ String actualName = graphWorkspace.getName();
+ addWorkspace(actualName, false);
+ }
+
+ /**
+ * Destroy the workspace with the supplied name.
+ *
+ * @param workspaceName the name of the workspace to be destroyed; may not be null
+ * @param currentWorkspace the workspace performing the operation; may not be null
+ * @throws InvalidWorkspaceException if the workspace cannot be destroyed
+ * @throws UnsupportedRepositoryOperationException if this repository does not support workspace management
+ * @throws NoSuchWorkspaceException if the workspace does not exist
+ * @throws RepositorySourceException if there is an error destroying this workspace
+ */
+ protected void destroyWorkspace( String workspaceName,
+ JcrWorkspace currentWorkspace )
+ throws InvalidWorkspaceException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException,
+ RepositorySourceException {
+ assert workspaceName != null;
+ assert currentWorkspace != null;
+ if (!Boolean.parseBoolean(getDescriptor(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED))) {
+ throw new UnsupportedRepositoryOperationException();
+ }
+ if (workspaceName.equals(systemWorkspaceName)) {
+ // Cannot create a workspace that has the same name as the system workspace ...
+ String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(workspaceName, sourceName);
+ throw new InvalidWorkspaceException(msg);
+ }
+ if (currentWorkspace.getName().equals(workspaceName)) {
+ String msg = GraphI18n.currentWorkspaceCannotBeDeleted.text(workspaceName, sourceName);
+ throw new InvalidWorkspaceException(msg);
+ }
+ // Make sure the workspace exists ...
+ Graph graph = Graph.create(sourceName, connectionFactory, executionContext);
+ graph.useWorkspace(currentWorkspace.getName());
+ if (!graph.getWorkspaces().contains(workspaceName)) {
+ // Cannot create a workspace that has the same name as the system workspace ...
+ String msg = GraphI18n.workspaceDoesNotExistInRepository.text(workspaceName, sourceName);
+ throw new NoSuchWorkspaceException(msg);
+ }
+
+ // Remove the federated workspace ...
+ if (federatedSource != null) {
+ synchronized (federatedSource) {
+ federatedSource.removeWorkspace(workspaceName);
+ }
+ }
+
+ // And now destroy the workspace ...
+ graph.destroyWorkspace().named(workspaceName);
+ }
+
+ protected void initializeSystemContent( Graph systemGraph ) {
+ // Make sure the "/jcr:system" node exists ...
+ ExecutionContext context = systemGraph.getContext();
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+ Path systemPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM);
+ Property systemPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.SYSTEM);
+ systemGraph.create(systemPath, systemPrimaryType).ifAbsent().and();
+
+ // Make sure the required jcr:versionStorage node exists...
+ Path versionPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, JcrLexicon.VERSION_STORAGE);
+ Property versionPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE,
+ ModeShapeLexicon.VERSION_STORAGE);
+ systemGraph.create(versionPath, versionPrimaryType).ifAbsent().and();
+
+ // Right now, the other nodes will be created as needed
+ }
+
+ JcrGraph createWorkspaceGraph( String workspaceName,
+ ExecutionContext workspaceContext ) {
+ JcrGraph graph = null;
+ if (WORKSPACES_SHARE_SYSTEM_BRANCH) {
+ // Connect via the federated source ...
+ assert this.federatedSource != null;
+ graph = JcrGraph.create(this.federatedSource, workspaceContext);
+ } else {
+ // Otherwise, just create a graph directly to the connection factory ...
+ graph = JcrGraph.create(this.sourceName, this.connectionFactory, workspaceContext);
+ }
+ graph.useWorkspace(workspaceName);
+ return graph;
+ }
+
+ JcrGraph createSystemGraph( ExecutionContext sessionContext ) {
+ assert this.systemSourceName != null;
+ assert this.connectionFactory != null;
+ assert sessionContext != null;
+ // The default workspace should be the system workspace ...
+ JcrGraph result = JcrGraph.create(this.systemSourceName, this.connectionFactory, sessionContext);
+ if (this.systemWorkspaceName != null) {
+ result.useWorkspace(systemWorkspaceName);
+ }
+ return result;
+ }
+
+ QueryParsers queryParsers() {
+ return queryParsers;
+ }
+
+ /**
+ * Get the query manager for this repository.
+ *
+ * @return the query manager; never null
+ */
+ RepositoryQueryManager queryManager() {
+ return queryManager;
+ }
+
+ /**
+ * Returns the repository-level node type manager
+ *
+ * @return the repository-level node type manager
+ */
+ RepositoryNodeTypeManager getRepositoryTypeManager() {
+ return repositoryTypeManager;
+ }
+
+ /**
+ * Returns the repository-level lock manager
+ *
+ * @return the repository-level lock manager
+ */
+ RepositoryLockManager getRepositoryLockManager() {
+ return repositoryLockManager;
+ }
+
+ /**
+ * Get the options as configured for this repository.
+ *
+ * @return the unmodifiable options; never null
+ */
+ public Map getOptions() {
+ return options;
+ }
+
+ /**
+ * Get the name of the repository source that this repository is using.
+ *
+ * @return the name of the RepositorySource
+ */
+ String getRepositorySourceName() {
+ return sourceName;
+ }
+
+ String getSystemSourceName() {
+ return systemSourceName;
+ }
+
+ String getSystemWorkspaceName() {
+ return systemWorkspaceName;
+ }
+
+ /**
+ * Get the name of the source that we want to observe.
+ *
+ * @return the name of the source that should be observed; never null
+ */
+ String getObservableSourceName() {
+ if (!WORKSPACES_SHARE_SYSTEM_BRANCH) {
+ return sourceName;
+ }
+ return federatedSource.getName();
+ }
+
+ /**
+ * @return executionContext
+ */
+ ExecutionContext getExecutionContext() {
+ return executionContext;
+ }
+
+ /**
+ * @return persistentRegistry
+ */
+ NamespaceRegistry getPersistentRegistry() {
+ return persistentRegistry;
+ }
+
+ /**
+ * The observer to which all source events are sent.
+ *
+ * @return the current observer, or null if there is no observer
+ */
+ Observer getObserver() {
+ return this.repositoryObservationManager;
+ }
+
+ /**
+ * The repository observable that listeners can be registered with.
+ *
+ * @return the repository observable (never null
)
+ */
+ Observable getRepositoryObservable() {
+ return this.repositoryObservationManager;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if key
is null
.
+ * @see Repository#getDescriptor(java.lang.String)
+ */
+ public String getDescriptor( String key ) {
+ if (!isSingleValueDescriptor(key)) return null;
+
+ JcrValue value = (JcrValue)descriptors.get(key);
+ try {
+ return value.getString();
+ } catch (RepositoryException re) {
+ throw new IllegalStateException(re);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if {@code key} is null or empty
+ * @see Repository#getDescriptorValue(String)
+ */
+ public JcrValue getDescriptorValue( String key ) {
+ if (!isSingleValueDescriptor(key)) return null;
+ return (JcrValue)descriptors.get(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if {@code key} is null or empty
+ * @see Repository#getDescriptorValues(String)
+ */
+ public JcrValue[] getDescriptorValues( String key ) {
+ Object value = descriptors.get(key);
+ if (value instanceof JcrValue[]) {
+ return (JcrValue[])value;
+ }
+ if (value instanceof JcrValue) {
+ return new JcrValue[] {(JcrValue)value};
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if {@code key} is null or empty
+ * @see Repository#isSingleValueDescriptor(String)
+ */
+ public boolean isSingleValueDescriptor( String key ) {
+ CheckArg.isNotEmpty(key, "key");
+ return descriptors.get(key) instanceof JcrValue;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if {@code key} is null or empty
+ * @see Repository#isStandardDescriptor(String)
+ */
+ public boolean isStandardDescriptor( String key ) {
+ return STANDARD_DESCRIPTORS.contains(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.Repository#getDescriptorKeys()
+ */
+ public String[] getDescriptorKeys() {
+ return descriptors.keySet().toArray(new String[descriptors.size()]);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.Repository#login()
+ */
+ public synchronized Session login() throws RepositoryException {
+ return login(null, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.Repository#login(javax.jcr.Credentials)
+ */
+ public synchronized Session login( Credentials credentials ) throws RepositoryException {
+ return login(credentials, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.Repository#login(java.lang.String)
+ */
+ public synchronized Session login( String workspaceName ) throws RepositoryException {
+ return login(null, workspaceName);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if credentials
is not null
but:
+ *
+ * provides neither a getLoginContext()
nor a getAccessControlContext()
method and is
+ * not an instance of {@code SimpleCredentials}.
+ * provides a getLoginContext()
method that doesn't return a {@link LoginContext}.
+ * provides a getLoginContext()
method that returns a
+ * null
{@link LoginContext}.
+ * does not provide a getLoginContext()
method, but provides a getAccessControlContext()
+ * method that doesn't return an {@link AccessControlContext}.
+ * does not provide a getLoginContext()
method, but provides a getAccessControlContext()
+ * method that returns a null
{@link AccessControlContext}.
+ *
+ * @see javax.jcr.Repository#login(javax.jcr.Credentials, java.lang.String)
+ */
+ public synchronized Session login( Credentials credentials,
+ String workspaceName ) throws RepositoryException {
+ // Ensure credentials are either null or provide a JAAS method
+ Map sessionAttributes = new HashMap();
+ ExecutionContext execContext = null;
+ if (credentials == null) {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ if (subject != null) {
+ execContext = executionContext.with(new JaasSecurityContext(subject));
+ }
+ // Well. There's no JAAS subject. Try using an anonymous user (if that's enabled).
+ else if (anonymousUserContext != null) {
+ execContext = executionContext.with(this.anonymousUserContext);
+ } else {
+ throw new javax.jcr.LoginException(JcrI18n.mustBeInPrivilegedAction.text());
+ }
+ } else {
+ try {
+ if (credentials instanceof SimpleCredentials) {
+ SimpleCredentials simple = (SimpleCredentials)credentials;
+ execContext = executionContext.with(new JaasSecurityContext(options.get(Option.JAAS_LOGIN_CONFIG_NAME),
+ simple.getUserID(), simple.getPassword()));
+ for (String attributeName : simple.getAttributeNames()) {
+ Object attributeValue = simple.getAttribute(attributeName);
+ sessionAttributes.put(attributeName, attributeValue);
+ }
+ } else if (credentials instanceof SecurityContextCredentials) {
+ execContext = executionContext.with(contextFor((SecurityContextCredentials)credentials));
+ } else if (credentials instanceof JcrSecurityContextCredentials) {
+ execContext = executionContext.with(((JcrSecurityContextCredentials)credentials).getSecurityContext());
+ } else {
+ // Check if credentials provide a login context
+ try {
+ Method method = credentials.getClass().getMethod("getLoginContext");
+ if (method.getReturnType() != LoginContext.class) {
+ throw new IllegalArgumentException(
+ JcrI18n.credentialsMustReturnLoginContext.text(credentials.getClass()));
+ }
+ LoginContext loginContext = (LoginContext)method.invoke(credentials);
+ if (loginContext == null) {
+ throw new IllegalArgumentException(
+ JcrI18n.credentialsMustReturnLoginContext.text(credentials.getClass()));
+ }
+ execContext = executionContext.with(new JaasSecurityContext(loginContext));
+ } catch (NoSuchMethodException error) {
+ throw new IllegalArgumentException(JcrI18n.credentialsMustProvideJaasMethod.text(credentials.getClass()),
+ error);
+ }
+ }
+ } catch (RuntimeException error) {
+ throw error;
+ } catch (Exception error) {
+ throw new javax.jcr.LoginException(error);
+ }
+ }
+ return sessionForContext(execContext, workspaceName, sessionAttributes);
+ }
+
+ /**
+ * Adapts the modeshape-jcr-api {@link org.modeshape.jcr.api.SecurityContext} to the modeshape-graph {@link SecurityContext}
+ * needed for repository login.
+ *
+ * @param credentials the credentials containing the modeshape-jcr-api {@code SecurityContext}
+ * @return an equivalent modeshape-graph {@code SecurityContext}
+ */
+ private SecurityContext contextFor( SecurityContextCredentials credentials ) {
+ assert credentials != null;
+
+ final org.modeshape.jcr.api.SecurityContext jcrSecurityContext = credentials.getSecurityContext();
+ assert jcrSecurityContext != null;
+
+ return new SecurityContext() {
+ @Override
+ public String getUserName() {
+ return jcrSecurityContext.getUserName();
+ }
+
+ @Override
+ public boolean hasRole( String roleName ) {
+ return jcrSecurityContext.hasRole(roleName);
+ }
+
+ @Override
+ public void logout() {
+ jcrSecurityContext.logout();
+ }
+
+ };
+ }
+
+ /**
+ * Creates a new {@link JcrSession session} based on the given {@link ExecutionContext context} and its associated security
+ * context.
+ *
+ * @param execContext the execution context to use for the new session; may not be null and must have a non-null
+ * {@link ExecutionContext#getSecurityContext() security context}
+ * @param workspaceName the name of the workspace to connect to; null indicates that the default workspace should be used
+ * @param sessionAttributes the session attributes for this session; may not be null
+ * @return a valid session for the user to access the repository
+ * @throws RepositoryException if an error occurs creating the session
+ */
+ JcrSession sessionForContext( ExecutionContext execContext,
+ String workspaceName,
+ Map sessionAttributes ) throws RepositoryException {
+ CheckArg.isNotNull(execContext.getSecurityContext(), "execContext.securityContext");
+ // Ensure valid workspace name by talking directly to the source ...
+ boolean isDefault = false;
+ Graph graph = Graph.create(sourceName, connectionFactory, executionContext);
+ if (workspaceName == null) {
+ try {
+ // Get the correct workspace name given the desired workspace name (which may be null) ...
+ workspaceName = graph.getCurrentWorkspace().getName();
+ } catch (RepositorySourceException e) {
+ throw new RepositoryException(JcrI18n.errorObtainingDefaultWorkspaceName.text(sourceName, e.getMessage()), e);
+ }
+ isDefault = true;
+ } else {
+ // There is a non-null workspace name ...
+ try {
+ // Verify that the workspace exists (or can be created) ...
+ Set workspaces = graph.getWorkspaces();
+ if (!workspaces.contains(workspaceName)) {
+ if (WORKSPACES_SHARE_SYSTEM_BRANCH) {
+ // Make sure there isn't a federated workspace ...
+ this.federatedSource.removeWorkspace(workspaceName);
+ }
+ // Per JCR 1.0 6.1.1, if the workspaceName is not recognized, a NoSuchWorkspaceException is thrown
+ // JCR 2.0 does not explicitely state the behavior if the workspace name is not found, though the JavaDoc
+ // does.
+ throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(sourceName, workspaceName));
+ }
+
+ graph.useWorkspace(workspaceName);
+ } catch (InvalidWorkspaceException e) {
+ throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(sourceName, workspaceName), e);
+ } catch (RepositorySourceException e) {
+ String msg = JcrI18n.errorVerifyingWorkspaceName.text(sourceName, workspaceName, e.getMessage());
+ throw new NoSuchWorkspaceException(msg, e);
+ }
+ }
+
+ if (WORKSPACES_SHARE_SYSTEM_BRANCH) {
+ addWorkspace(workspaceName, isDefault);
+ } else {
+ // We're not sharing a '/jcr:system' branch, so we need to make sure there is one in the source.
+ // Note that this doesn't always work with some connectors (e.g., the FileSystem or SVN connectors)
+ // that don't allow arbitrary nodes.
+ try {
+ initializeSystemContent(graph);
+ } catch (RepositorySourceException e) {
+ Logger.getLogger(getClass())
+ .debug(e,
+ "Workspaces do not share a common /jcr:system branch, but the connector was unable to create one in this session. Errors may result.");
+ }
+ }
+
+ // Create the workspace, which will create its own session ...
+ sessionAttributes = Collections.unmodifiableMap(sessionAttributes);
+ JcrWorkspace workspace = new JcrWorkspace(this, workspaceName, execContext, sessionAttributes);
+
+ JcrSession session = (JcrSession)workspace.getSession();
+
+ // Need to make sure that the user has access to this session
+ try {
+ session.checkPermission(workspaceName, null, ModeShapePermissions.READ);
+ } catch (AccessControlException ace) {
+ throw new LoginException(JcrI18n.workspaceNameIsInvalid.text(sourceName, workspaceName), ace);
+ }
+
+ synchronized (this.activeSessions) {
+ activeSessions.put(session, null);
+ }
+
+ return session;
+ }
+
+ void close() {
+ if (this.federatedSource != null) {
+ this.federatedSource.close();
+ }
+
+ this.repositoryObservationManager.shutdown();
+ }
+
+ /**
+ * @return a list of all workspace names, without regard to the access permissions of any particular user
+ */
+ Set workspaceNames() {
+ return Graph.create(sourceName, connectionFactory, executionContext).getWorkspaces();
+ }
+
+ /**
+ * Marks the given session as inactive (by removing it from the {@link #activeSessions active sessions map}.
+ *
+ * @param session the session to be marked as inactive
+ */
+ void sessionLoggedOut( JcrSession session ) {
+ synchronized (this.activeSessions) {
+ this.activeSessions.remove(session);
+ }
+ }
+
+ /**
+ * Returns the set of active sessions in this repository
+ *
+ * @return the set of active sessions in this repository
+ */
+ Set activeSessions() {
+ Set activeSessions;
+
+ synchronized (this.activeSessions) {
+ activeSessions = new HashSet(this.activeSessions.keySet());
+ }
+ // There can and will be elements in this set that are no longer live but haven't yet been gc'ed.
+ // Filter those out
+ for (Iterator iter = activeSessions.iterator(); iter.hasNext();) {
+ JcrSession session = iter.next();
+ if (session != null && !session.isLive()) {
+ iter.remove();
+ }
+ }
+
+ return activeSessions;
+ }
+
+ /**
+ * Get the component that exposes the metrics of a repository, which is for internal use only. This mechanism will almost
+ * certainly change or be replaced in an upcoming release.
+ *
+ * @return the metrics interface; never null
+ */
+ public Metrics getMetrics() {
+ return new Metrics();
+ }
+
+ /**
+ * Terminate all active sessions.
+ */
+ void terminateAllSessions() {
+ synchronized (this.activeSessions) {
+ for (JcrSession session : this.activeSessions.keySet()) {
+ session.terminate(false); // don't remove from active sessions, as we're blocked and iterating on it ...
+ }
+ this.activeSessions.clear();
+ }
+ }
+
+ protected static Properties getBundleProperties() {
+ if (bundleProperties == null) {
+ // This is idempotent, so we don't need to lock ...
+ InputStream stream = null;
+ try {
+ stream = JcrRepository.class.getClassLoader().getResourceAsStream("org/modeshape/jcr/repository.properties");
+ assert stream != null;
+ Properties props = new Properties();
+ props.load(stream);
+ bundleProperties = new UnmodifiableProperties(props);
+ } catch (IOException e) {
+ throw new IllegalStateException(JcrI18n.failedToReadPropertiesFromManifest.text(e.getLocalizedMessage()), e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ } finally {
+ stream = null;
+ }
+ }
+ }
+ }
+ return bundleProperties;
+ }
+
+ protected static String getBundleProperty( String propertyName,
+ boolean required ) {
+ String value = getBundleProperties().getProperty(propertyName);
+ if (value == null && required) {
+ throw new IllegalStateException(JcrI18n.failedToReadPropertyFromManifest.text(propertyName));
+ }
+ return value;
+ }
+
+ /**
+ * Combines the given custom descriptors with the default repository descriptors.
+ *
+ * @param factories the value factories to use to create the descriptor values
+ * @param customDescriptors the custom descriptors; may be null
+ * @return the custom descriptors (if any) combined with the default repository descriptors; never null or empty
+ */
+ @SuppressWarnings( "deprecation" )
+ private static Map initializeDescriptors( ValueFactories factories,
+ Map customDescriptors ) {
+ if (customDescriptors == null) customDescriptors = Collections.emptyMap();
+ Map repoDescriptors = new HashMap(customDescriptors.size() + 60);
+
+ for (Map.Entry entry : customDescriptors.entrySet()) {
+ repoDescriptors.put(entry.getKey(), valueFor(factories, entry.getValue()));
+ }
+
+ repoDescriptors.put(Repository.LEVEL_1_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.LEVEL_2_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_LOCKING_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_OBSERVATION_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_QUERY_SQL_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_TRANSACTIONS_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_VERSIONING_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.QUERY_XPATH_DOC_ORDER, valueFor(factories, false)); // see MODE-613
+ repoDescriptors.put(Repository.QUERY_XPATH_POS_INDEX, valueFor(factories, true));
+
+ repoDescriptors.put(Repository.WRITE_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.IDENTIFIER_STABILITY, valueFor(factories, Repository.IDENTIFIER_STABILITY_METHOD_DURATION));
+ repoDescriptors.put(Repository.OPTION_XML_IMPORT_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_XML_EXPORT_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_UNFILED_CONTENT_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_SIMPLE_VERSIONING_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_ACTIVITIES_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_BASELINES_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_ACCESS_CONTROL_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_JOURNALED_OBSERVATION_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_RETENTION_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_LIFECYCLE_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_NODE_AND_PROPERTY_WITH_SAME_NAME_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_UPDATE_PRIMARY_NODE_TYPE_SUPPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.OPTION_UPDATE_MIXIN_NODE_TYPES_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_SHAREABLE_NODES_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.OPTION_NODE_TYPE_MANAGEMENT_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_INHERITANCE,
+ valueFor(factories, Repository.NODE_TYPE_MANAGEMENT_INHERITANCE_MULTIPLE));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_PRIMARY_ITEM_NAME_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_ORDERABLE_CHILD_NODES_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_RESIDUAL_DEFINITIONS_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_AUTOCREATED_DEFINITIONS_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_SAME_NAME_SIBLINGS_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_PROPERTY_TYPES, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_OVERRIDES_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_MULTIVALUED_PROPERTIES_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_MULTIPLE_BINARY_PROPERTIES_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_VALUE_CONSTRAINTS_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.NODE_TYPE_MANAGEMENT_UPDATE_IN_USE_SUPORTED, valueFor(factories, false));
+ repoDescriptors.put(Repository.QUERY_LANGUAGES,
+ new JcrValue[] {valueFor(factories, Query.XPATH), valueFor(factories, JcrSql2QueryParser.LANGUAGE),
+ valueFor(factories, Query.SQL), valueFor(factories, Query.JCR_JQOM)});
+ repoDescriptors.put(Repository.QUERY_STORED_QUERIES_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.QUERY_FULL_TEXT_SEARCH_SUPPORTED, valueFor(factories, true));
+ repoDescriptors.put(Repository.QUERY_JOINS, valueFor(factories, Repository.QUERY_JOINS_INNER_OUTER));
+ repoDescriptors.put(Repository.SPEC_NAME_DESC, valueFor(factories, JcrI18n.SPEC_NAME_DESC.text()));
+ repoDescriptors.put(Repository.SPEC_VERSION_DESC, valueFor(factories, "2.0"));
+
+ if (!repoDescriptors.containsKey(Repository.REP_NAME_DESC)) {
+ repoDescriptors.put(Repository.REP_NAME_DESC,
+ valueFor(factories, JcrRepository.getBundleProperty(Repository.REP_NAME_DESC, true)));
+ }
+ if (!repoDescriptors.containsKey(Repository.REP_VENDOR_DESC)) {
+ repoDescriptors.put(Repository.REP_VENDOR_DESC, valueFor(factories,
+ JcrRepository.getBundleProperty(Repository.REP_VENDOR_DESC,
+ true)));
+ }
+ if (!repoDescriptors.containsKey(Repository.REP_VENDOR_URL_DESC)) {
+ repoDescriptors.put(Repository.REP_VENDOR_URL_DESC,
+ valueFor(factories, JcrRepository.getBundleProperty(Repository.REP_VENDOR_URL_DESC, true)));
+ }
+ if (!repoDescriptors.containsKey(Repository.REP_VERSION_DESC)) {
+ repoDescriptors.put(Repository.REP_VERSION_DESC,
+ valueFor(factories, JcrRepository.getBundleProperty(Repository.REP_VERSION_DESC, true)));
+ }
+
+ if (!repoDescriptors.containsKey(Repository.REP_VERSION_DESC)) {
+ repoDescriptors.put(Repository.REP_VERSION_DESC,
+ valueFor(factories, JcrRepository.getBundleProperty(Repository.REP_VERSION_DESC, true)));
+ }
+ if (!repoDescriptors.containsKey(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED)) {
+ repoDescriptors.put(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED, valueFor(factories, true));
+ }
+
+ return Collections.unmodifiableMap(repoDescriptors);
+ }
+
+ private static JcrValue valueFor( ValueFactories valueFactories,
+ int type,
+ Object value ) {
+ return new JcrValue(valueFactories, null, type, value);
+ }
+
+ private static JcrValue valueFor( ValueFactories valueFactories,
+ String value ) {
+ return valueFor(valueFactories, PropertyType.STRING, value);
+ }
+
+ private static JcrValue valueFor( ValueFactories valueFactories,
+ boolean value ) {
+ return valueFor(valueFactories, PropertyType.BOOLEAN, value);
+ }
+
+ protected class FederatedRepositoryContext implements RepositoryContext {
+ private final RepositoryConnectionFactory connectionFactory;
+
+ protected FederatedRepositoryContext( RepositoryConnectionFactory nonFederatingConnectionFactory ) {
+ this.connectionFactory = nonFederatingConnectionFactory;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.connector.RepositoryContext#getConfiguration(int)
+ */
+ public Subgraph getConfiguration( int depth ) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.connector.RepositoryContext#getExecutionContext()
+ */
+ public ExecutionContext getExecutionContext() {
+ return JcrRepository.this.getExecutionContext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.connector.RepositoryContext#getObserver()
+ */
+ public Observer getObserver() {
+ return JcrRepository.this.getObserver();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.connector.RepositoryContext#getRepositoryConnectionFactory()
+ */
+ public RepositoryConnectionFactory getRepositoryConnectionFactory() {
+ return connectionFactory;
+ }
+ }
+
+ protected class DelegatingConnectionFactory implements RepositoryConnectionFactory {
+ private final RepositoryConnectionFactory delegate;
+ private final RepositorySource source;
+
+ protected DelegatingConnectionFactory( RepositoryConnectionFactory delegate,
+ RepositorySource source ) {
+ assert delegate != null;
+ this.delegate = delegate;
+ this.source = source;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.connector.RepositoryConnectionFactory#createConnection(java.lang.String)
+ */
+ public RepositoryConnection createConnection( String sourceName ) throws RepositorySourceException {
+ if (this.source.getName().equals(sourceName)) {
+ return this.source.getConnection();
+ }
+ return delegate.createConnection(sourceName);
+ }
+ }
+
+ @Immutable
+ protected static class SourceWorkspacePair {
+ private final String sourceName;
+ private final String workspaceName;
+
+ protected SourceWorkspacePair( String sourceAndWorkspaceName ) {
+ assert sourceAndWorkspaceName != null;
+ sourceAndWorkspaceName = sourceAndWorkspaceName.trim();
+ assert sourceAndWorkspaceName.length() != 0;
+ sourceAndWorkspaceName = sourceAndWorkspaceName.trim();
+ // Look for the first '@' not preceded by a '\' ...
+ int maxIndex = sourceAndWorkspaceName.length() - 1;
+ int index = sourceAndWorkspaceName.indexOf('@');
+ while (index > 0 && index < maxIndex && sourceAndWorkspaceName.charAt(index - 1) == '\\') {
+ index = sourceAndWorkspaceName.indexOf('@', index + 1);
+ }
+ if (index > 0) {
+ // There is a workspace and source name ...
+ workspaceName = sourceAndWorkspaceName.substring(0, index).trim().replaceAll("\\\\@", "@");
+ if (index < maxIndex) sourceName = sourceAndWorkspaceName.substring(index + 1).trim().replaceAll("\\\\@", "@");
+ else throw new IllegalArgumentException("The source name is invalid");
+ } else if (index == 0) {
+ // The '@' was used, but the workspace is empty
+ if (sourceAndWorkspaceName.length() == 1) {
+ sourceName = "";
+ } else {
+ sourceName = sourceAndWorkspaceName.substring(1).trim().replaceAll("\\\\@", "@");
+ }
+ workspaceName = "";
+ } else {
+ // There is just a source name...
+ workspaceName = null;
+ sourceName = sourceAndWorkspaceName.replaceAll("\\\\@", "@");
+ }
+ assert this.sourceName != null;
+ }
+
+ /**
+ * @return sourceName
+ */
+ public String getSourceName() {
+ return sourceName;
+ }
+
+ /**
+ * @return workspaceName
+ */
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ if (sourceName == null) return "";
+ if (workspaceName != null) {
+ return workspaceName + '@' + sourceName;
+ }
+ return sourceName;
+ }
+ }
+
+ protected class RepositoryObservationManager implements Observable, Observer {
+
+ private final ExecutorService observerService = Executors.newSingleThreadExecutor();
+ private final CopyOnWriteArrayList observers = new CopyOnWriteArrayList();
+ private final Observable repositoryObservable;
+ private final String sourceName;
+ private final String systemSourceName;
+ private final String repositorySourceName;
+ private final String processId;
+
+ /**
+ * @param repositoryObservable the repository library observable this observer should register with
+ */
+ protected RepositoryObservationManager( Observable repositoryObservable ) {
+ this.repositoryObservable = repositoryObservable;
+ this.repositoryObservable.register(this);
+ this.sourceName = getObservableSourceName();
+ this.systemSourceName = getSystemSourceName();
+ this.repositorySourceName = getRepositorySourceName();
+ this.processId = getExecutionContext().getProcessId();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.observe.Observer#notify(org.modeshape.graph.observe.Changes)
+ */
+ public void notify( Changes changes ) {
+ final Changes acceptableChanges = filter(changes);
+ if (acceptableChanges != null) {
+
+ // We're still in the thread where the connector published its changes,
+ // so we need to create a runnable that will send these changes to all
+ // of the observers at this moment . Because 'observers' is
+ // a CopyOnWriteArrayList, we can't old onto the list (because the list's content
+ // might change). Instead, hold onto the Iterator over the listeners,
+ // and that will be a snapshot of the listeners at this moment
+ if (observers.isEmpty()) return;
+ final Iterator observerIterator = observers.iterator();
+
+ Runnable sender = new Runnable() {
+ public void run() {
+ while (observerIterator.hasNext()) {
+ Observer observer = observerIterator.next();
+ assert observer != null;
+ observer.notify(acceptableChanges);
+ }
+ }
+ };
+
+ // Now let the executor service run this in another thread ...
+ this.observerService.execute(sender);
+ }
+ }
+
+ private Changes filter( Changes changes ) {
+ // We only care about events that come from the repository source, the system source,
+ // or the repository source name (for remote events only) ...
+ String changedSourceName = changes.getSourceName();
+ if (sourceName.equals(changedSourceName)) {
+ // These are changes made locally by this repository ...
+ return changes;
+ }
+ if (repositorySourceName.equals(changedSourceName)) {
+ // These may be events generated locally or from a remote engine in the cluster ...
+ if (this.processId.equals(changes.getProcessId())) {
+ // These events were made locally and are being handled above, so we can ignore these ...
+ return null;
+ }
+ // Otherwise, the changes were received from another engine in the cluster and
+ // we do want to respond to these changes. However, the source name of the changes
+ // needs to be altered to match the 'sourceName' ...
+ return new Changes(changes.getProcessId(), changes.getContextId(), changes.getUserName(), sourceName,
+ changes.getTimestamp(), changes.getChangeRequests(), changes.getData());
+ }
+ assert !changedSourceName.equals(repositorySourceName);
+ if (systemSourceName.equals(changedSourceName)) {
+ // These are changes made locally by this repository ...
+ return changes;
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.observe.Observable#register(org.modeshape.graph.observe.Observer)
+ */
+ public boolean register( Observer observer ) {
+ if (observer == null) return false;
+ return this.observers.addIfAbsent(observer);
+ }
+
+ /**
+ * Must be called to shutdown the service that is used to notify the observers.
+ */
+ void shutdown() {
+ synchronized (this) {
+ this.repositoryObservable.unregister(this);
+ this.observers.clear();
+ this.observerService.shutdown();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.observe.Observable#unregister(org.modeshape.graph.observe.Observer)
+ */
+ public boolean unregister( Observer observer ) {
+ if (observer == null) return false;
+ return this.observers.remove(observer);
+ }
+ }
+
+ /**
+ * This component is used internally to expose the metrics of a repository, and should not be used outside of the ModeShape
+ * codebase, as it will almost certainly change or be replaced in upcoming releases.
+ */
+ public class Metrics {
+ /**
+ * Get the number of currently-active sessions in this repository.
+ *
+ * @return the number of current, active sessions; always non-negative.
+ */
+ public int getActiveSessionCount() {
+ synchronized (activeSessions) {
+ return activeSessions.size();
+ }
+ }
+ }
+
+ /**
+ * Observer that forwards changes to the system workspace in the system repository source to registered
+ * {@link JcrSystemObserver JcrSystemObservers}. This method is intended to support consistency of internal data structures in
+ * a cluster and only forwards changes from other processes.
+ */
+ class SystemChangeObserver implements Observer {
+
+ /**
+ * Immutable collection of objects observing changes to the system graph
+ */
+ private final Collection jcrSystemObservers;
+ private final String processId;
+ private final String systemSourceName;
+ private final String systemWorkspaceName;
+
+ SystemChangeObserver( Collection jcrSystemObservers ) {
+ this.jcrSystemObservers = Collections.unmodifiableCollection(jcrSystemObservers);
+ processId = getExecutionContext().getProcessId();
+ systemSourceName = getSystemSourceName();
+ systemWorkspaceName = getSystemWorkspaceName();
+
+ assert processId != null;
+ assert systemSourceName != null;
+ assert systemWorkspaceName != null;
+ }
+
+ @Override
+ public void notify( Changes changes ) {
+ // Don't process changes from outside the system graph
+ if (!changes.getSourceName().equals(systemSourceName)) {
+ /*
+ * It's permissable for the system source to be the same as the source for the workspaces.
+ * In that case, the RepositoryObservationManager would have already translated the system
+ * source name into the observeable source name (from getObservableSourceName()), obscuring
+ * the actual source.
+ *
+ * So if the change source name doesn't equal the system source name BUT the system source name
+ * is the same as the repository (read: workspace) source name, don't give up yet. The difference
+ * may be due to RepositoryObservationManager. Rely on the systemWorkspaceName check below to
+ * be sure.
+ */
+ if (!systemSourceName.equals(getRepositorySourceName())) return;
+ }
+
+ // Don't process changes from this repository
+ if (changes.getProcessId().equals(processId)) return;
+
+ Multimap systemChanges = LinkedListMultimap.create();
+
+ for (ChangeRequest change : changes.getChangeRequests()) {
+ // Don't process changes from outside the system workspace
+ if (!systemWorkspaceName.equals(change.changedWorkspace())) continue;
+
+ Path changedPath = change.changedLocation().getPath();
+ if (changedPath == null) continue;
+
+ for (JcrSystemObserver jcrSystemObserver : jcrSystemObservers) {
+ if (changedPath.isAtOrBelow(jcrSystemObserver.getObservedPath())) {
+ systemChanges.put(jcrSystemObserver, change);
+ }
+ }
+ }
+
+ // Parcel out the new change objects
+ for (JcrSystemObserver jcrSystemObserver : systemChanges.keySet()) {
+ List changesForObserver = (List)systemChanges.get(jcrSystemObserver);
+ Changes filteredChanges = new Changes(changes.getProcessId(), changes.getContextId(), changes.getUserName(),
+ systemSourceName, changes.getTimestamp(), changesForObserver,
+ changes.getData());
+ jcrSystemObserver.notify(filteredChanges);
+ }
+ }
+ }
+}
Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrConfigurationTest.java
===================================================================
--- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrConfigurationTest.java (revision 2489)
+++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrConfigurationTest.java (working copy)
@@ -257,6 +257,7 @@ public class JcrConfigurationTest {
options.put(Option.QUERY_INDEX_DIRECTORY, DefaultOption.QUERY_INDEX_DIRECTORY);
options.put(Option.QUERY_INDEXES_UPDATED_SYNCHRONOUSLY, DefaultOption.QUERY_INDEXES_UPDATED_SYNCHRONOUSLY);
options.put(Option.PERFORM_REFERENTIAL_INTEGRITY_CHECKS, DefaultOption.PERFORM_REFERENTIAL_INTEGRITY_CHECKS);
+ options.put(Option.EXPOSE_WORKSPACE_NAMES_IN_DESCRIPTOR, DefaultOption.EXPOSE_WORKSPACE_NAMES_IN_DESCRIPTOR);
assertThat(repository.getOptions(), is(options));
}
Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java
===================================================================
--- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (revision 2489)
+++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (working copy)
@@ -26,19 +26,23 @@ package org.modeshape.jcr;
import static org.hamcrest.collection.IsArrayContaining.hasItemInArray;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.TimeUnit;
import javax.jcr.Credentials;
import javax.jcr.Repository;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
+import javax.jcr.ValueFormatException;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import org.jboss.security.config.IDTrustConfiguration;
@@ -201,6 +205,31 @@ public class JcrRepositoryTest {
}
@Test
+ public void shouldProvideRepositoryWorkspaceNamesDescriptor() throws ValueFormatException {
+ Set workspaceNames = repository.workspaceNames();
+ Set descriptorValues = new HashSet();
+ for (JcrValue value : repository.getDescriptorValues(org.modeshape.jcr.api.Repository.REPOSITORY_WORKSPACES)) {
+ descriptorValues.add(value.getString());
+ }
+ assertThat(descriptorValues, is(workspaceNames));
+ }
+
+ @Test
+ public void shouldNotProvideRepositoryWorkspaceNamesDescriptorIfOptionSetToFalse() throws Exception {
+ JcrConfiguration config = new JcrConfiguration();
+ config.repositorySource("Store").usingClass(InMemoryRepositorySource.class);
+ config.repository("JCR").setOption(JcrRepository.Option.EXPOSE_WORKSPACE_NAMES_IN_DESCRIPTOR, Boolean.FALSE.toString()).setSource("Store");
+
+ JcrEngine engine = config.build();
+ engine.start();
+
+ assertThat(engine.getRepository("JCR").getDescriptor(org.modeshape.jcr.api.Repository.REPOSITORY_WORKSPACES),
+ is(nullValue()));
+
+ engine.shutdownAndAwaitTermination(3, TimeUnit.SECONDS);
+ }
+
+ @Test
public void shouldProvideObserver() {
assertThat(this.repository.getObserver(), is(notNullValue()));
}
@@ -283,8 +312,8 @@ public class JcrRepositoryTest {
@Test
public void shouldAllowLoginWithProperCredentials() throws Exception {
repository.login(credentials);
- repository.login(new JcrSecurityContextCredentials(
- new MockSecurityContext(null, Collections.singleton(ModeShapeRoles.ADMIN))));
+ repository.login(new JcrSecurityContextCredentials(new MockSecurityContext(null,
+ Collections.singleton(ModeShapeRoles.ADMIN))));
}
@Test
@@ -293,9 +322,9 @@ public class JcrRepositoryTest {
assertThat(session, notNullValue());
session.logout();
session = repository.login(new JcrSecurityContextCredentials(
- new MockSecurityContext(
- null,
- Collections.singleton(ModeShapeRoles.ADMIN))),
+ new MockSecurityContext(
+ null,
+ Collections.singleton(ModeShapeRoles.ADMIN))),
(String)null);
assertThat(session, notNullValue());
session.logout();