Index: docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml =================================================================== --- docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml (revision 1904) +++ docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml (working copy) @@ -230,9 +230,12 @@ final &LoginContext; loginContext = ...; ModeShape has extended the set of JCR-defined actions ("add_node", "set_property", "remove", and "read") with additional actions ("register_type", - "register_namespace", and "unlock_any"). The register_type and register_namespace permissions restrict the ability to register (and unregister) node types and namespaces, respectively. - The unlock_any permission grants the user the ability to unlock any locked node or branch (as opposed to users without that permission who can only unlock nodes or branches that they - have locked themselves or for which they hold the lock token). + "register_namespace", "unlock_any", "create_workspace" and "delete_workspace"). The "register_type" and "register_namespace" permissions control + the ability to register (and unregister) node types and namespaces, respectively. + The "unlock_any"" permission grants the user the ability to unlock any locked node or branch (as opposed to users without that permission + who can only unlock nodes or branches that they have locked themselves or for which they hold the lock token). + And the "create_workspace" and "delete_workspace" permissions grants the user the ability to create workspaces and delete workspaces, respectively, + using the corresponding methods on &Workspace;. Permissions to perform these actions are aggregated in roles that can be assigned to users. @@ -299,12 +302,24 @@ final &LoginContext; loginContext = ...; Allows + + create_workspace + + + Allows + + + delete_workspace + + + Allows + - In this release, ModeShape does not check that the actions parameter passed into + In this release, ModeShape does not check that the actions parameter passed into Session.checkPermission(...) contains only valid actions. This check may be added in a future release. @@ -342,8 +357,10 @@ final &LoginContext; loginContext = ...; - It is also possible to grant more than one role to the same user. For example, the user jsmith could be granted the roles readonly.production, readwrite.production.jsmith, - and readwrite.staging to allow read-only access to any workspace on a production repository, read/write access to a personal workspace on the same production repository, + It is also possible to grant more than one role to the same user. For example, the user "jsmith" could be granted the roles + "readonly.production", "readwrite.production.jsmith", + and "readwrite.staging" to allow read-only access to any workspace on a production repository, + read/write access to a personal workspace on the same production repository, and read/write access to any workspace in a staging repository. Index: modeshape-graph/src/main/java/org/modeshape/graph/Graph.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/Graph.java (revision 1904) +++ modeshape-graph/src/main/java/org/modeshape/graph/Graph.java (working copy) @@ -91,6 +91,7 @@ import org.modeshape.graph.request.CloneWorkspaceRequest; import org.modeshape.graph.request.CompositeRequest; import org.modeshape.graph.request.CreateNodeRequest; import org.modeshape.graph.request.CreateWorkspaceRequest; +import org.modeshape.graph.request.DestroyWorkspaceRequest; import org.modeshape.graph.request.FullTextSearchRequest; import org.modeshape.graph.request.InvalidRequestException; import org.modeshape.graph.request.InvalidWorkspaceException; @@ -430,6 +431,32 @@ public class Graph { } /** + * Destroy an existing workspace in the source used by this graph. It is not possible to destroy the workspace that this graph + * is {@link #getCurrentWorkspace() currently using}. + * + * @return the interface used to complete the request to create a new workspace; never null + */ + public DestroyWorkspace destroyWorkspace() { + return new DestroyWorkspace() { + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.Graph.DestroyWorkspace#named(java.lang.String) + */ + public boolean named( String workspaceName ) { + CheckArg.isNotNull(workspaceName, "workspaceName"); + if (getCurrentWorkspaceName().equals(workspaceName)) { + String msg = GraphI18n.currentWorkspaceCannotBeDeleted.text(workspaceName, getSourceName()); + throw new InvalidWorkspaceException(msg); + } + DestroyWorkspaceRequest request = requests.destroyWorkspace(workspaceName); + if (request.hasError()) return false; + return true; + } + }; + } + + /** * Request to lock the specified node. This request is submitted to the repository immediately. * * @param at the node that is to be locked @@ -4749,6 +4776,21 @@ public class Graph { } /** + * The interface used to destroy a workspace. + */ + public interface DestroyWorkspace { + /** + * Specify the name of the new workspace that is to be destroyed. + * + * @param workspaceName the name of the existing workspace that will be cloned to create the new workspace; + * @return true if the workspace was destroyed, or false otherwise + * @throws IllegalArgumentException if the name of the workspace is null + * @throws InvalidWorkspaceException if there is no existing workspace with the supplied name + */ + boolean named( String workspaceName ); + } + + /** * A interface used to execute the accumulated {@link Batch requests}. * * @param the type of node that is returned Index: modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (revision 1904) +++ modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (working copy) @@ -112,6 +112,7 @@ public final class GraphI18n { public static I18n pathConnectorRequestsMustHavePath; public static I18n workspaceDoesNotExistInRepository; public static I18n workspaceAlreadyExistsInRepository; + public static I18n currentWorkspaceCannotBeDeleted; public static I18n sourceIsReadOnly; public static I18n workspaceIsReadOnly; Index: modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties =================================================================== --- modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (revision 1904) +++ modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (working copy) @@ -100,6 +100,7 @@ inMemoryConnectorMustAllowUpdates = In-Memory connector "{0}" must allow updates pathConnectorRequestsMustHavePath = Path connectors can only process requests with a path workspaceDoesNotExistInRepository = The workspace "{0}" does not exist in the "{1}" repository workspaceAlreadyExistsInRepository = The workspace "{0}" already exists in the "{1}" repository +currentWorkspaceCannotBeDeleted = The workspace "{0}" is in use and cannot be removed from the "{1}" repository sourceIsReadOnly = The repository source "{0}" does not allow updates. Set the "updatesAllowed" property to "true" on the repository source to allow updates. workspaceIsReadOnly = The workspace "{1}" in repository source "{0}" does not allow updates. Setting the "updatesAllowed" property to "true" on the repository source may allow this workspace to support updates. Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ConfigurationTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ConfigurationTest.java (revision 1904) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ConfigurationTest.java (working copy) @@ -27,6 +27,7 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertThat; import java.io.File; +import java.util.Set; import java.util.concurrent.TimeUnit; import javax.jcr.Credentials; import javax.jcr.Repository; @@ -36,6 +37,7 @@ import org.jboss.security.config.IDTrustConfiguration; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.modeshape.common.collection.Collections; import org.modeshape.jcr.JcrConfiguration; import org.modeshape.jcr.JcrEngine; @@ -103,6 +105,17 @@ public class ConfigurationTest { Repository repository = engine.getRepository("magnolia"); assertThat(repository, is(notNullValue())); + // Get the predefined workspaces on the 'magnolia' repository source ... + Set magnoliaWorkspaces = engine.getGraph("magnolia").getWorkspaces(); + Set diskWorkspaces = engine.getGraph("disk").getWorkspaces(); + Set dataWorkspaces = engine.getGraph("data").getWorkspaces(); + + assertThat(magnoliaWorkspaces, is(Collections.unmodifiableSet(new String[] {"config", "website", "users", "userroles", + "usergroups", "mgnlSystem", "mgnlVersion", "downloads"}))); + assertThat(dataWorkspaces, is(Collections.unmodifiableSet(new String[] {"config", "website", "users", "userroles", + "usergroups", "mgnlSystem", "mgnlVersion", "modeSystem"}))); + assertThat(diskWorkspaces, is(Collections.unmodifiableSet(new String[] {"files"}))); + // Create a session, authenticating using one of the usernames defined by our JAAS policy file(s) ... Session session = null; Credentials credentials = new SimpleCredentials("superuser", "superuser".toCharArray()); @@ -112,6 +125,11 @@ public class ConfigurationTest { try { session = repository.login(credentials, workspaceName); session.getRootNode().addNode("testNode", "nt:folder"); + + // Check that the workspaces are all available ... + Set jcrWorkspaces = Collections.unmodifiableSet(session.getWorkspace().getAccessibleWorkspaceNames()); + assertThat(jcrWorkspaces, is(Collections.unmodifiableSet(new String[] {"config", "website", "users", "userroles", + "usergroups", "mgnlSystem", "mgnlVersion", "downloads"}))); } finally { if (session != null) session.logout(); } @@ -148,6 +166,17 @@ public class ConfigurationTest { Repository repository = engine.getRepository("data"); assertThat(repository, is(notNullValue())); + // Get the predefined workspaces on the 'magnolia' repository source ... + Set magnoliaWorkspaces = engine.getGraph("magnolia").getWorkspaces(); + Set diskWorkspaces = engine.getGraph("disk").getWorkspaces(); + Set dataWorkspaces = engine.getGraph("data").getWorkspaces(); + + assertThat(magnoliaWorkspaces, is(Collections.unmodifiableSet(new String[] {"config", "website", "users", "userroles", + "usergroups", "mgnlSystem", "mgnlVersion", "downloads"}))); + assertThat(dataWorkspaces, is(Collections.unmodifiableSet(new String[] {"config", "website", "users", "userroles", + "usergroups", "mgnlSystem", "mgnlVersion", "modeSystem"}))); + assertThat(diskWorkspaces, is(Collections.unmodifiableSet(new String[] {"files"}))); + // Create a session, authenticating using one of the usernames defined by our JAAS policy file(s) ... Session session = null; Credentials credentials = new SimpleCredentials("superuser", "superuser".toCharArray()); @@ -156,11 +185,15 @@ public class ConfigurationTest { try { session = repository.login(credentials, workspaceName); session.getRootNode().addNode("testNode", "nt:folder"); + + // Check that the workspaces are all available ... + Set jcrWorkspaces = Collections.unmodifiableSet(session.getWorkspace().getAccessibleWorkspaceNames()); + assertThat(jcrWorkspaces, is(Collections.unmodifiableSet(new String[] {"config", "website", "users", "userroles", + "usergroups", "mgnlSystem", "mgnlVersion"}))); } finally { if (session != null) session.logout(); } } - } } Index: modeshape-integration-tests/src/test/resources/config/federatingConfigRepository.xml =================================================================== --- modeshape-integration-tests/src/test/resources/config/federatingConfigRepository.xml (revision 1904) +++ modeshape-integration-tests/src/test/resources/config/federatingConfigRepository.xml (working copy) @@ -15,6 +15,7 @@ usergroups mgnlSystem mgnlVersion + modeSystem magnolia + data + Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision 1904) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy) @@ -52,6 +52,7 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; +import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.query.Query; import javax.security.auth.Subject; import javax.security.auth.login.Configuration; @@ -66,11 +67,13 @@ import org.modeshape.common.util.CheckArg; import org.modeshape.common.util.Logger; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Graph; +import org.modeshape.graph.GraphI18n; import org.modeshape.graph.JaasSecurityContext; import org.modeshape.graph.Location; import org.modeshape.graph.Node; import org.modeshape.graph.SecurityContext; import org.modeshape.graph.Subgraph; +import org.modeshape.graph.Workspace; import org.modeshape.graph.connector.RepositoryConnection; import org.modeshape.graph.connector.RepositoryConnectionFactory; import org.modeshape.graph.connector.RepositoryContext; @@ -448,9 +451,6 @@ public class JcrRepository implements Repository { CheckArg.isNotNull(repositorySourceName, "repositorySourceName"); CheckArg.isNotNull(repositoryObservable, "repositoryObservable"); - // Initialize required JCR descriptors. - this.descriptors = initializeDescriptors(executionContext.getValueFactories(), descriptors); - // Set up the options ... if (options == null) { this.options = DEFAULT_OPTIONS; @@ -585,11 +585,34 @@ public class JcrRepository implements Repository { 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); + this.lockManagers = new ConcurrentHashMap(); this.locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, ModeShapeLexicon.LOCKS); @@ -651,6 +674,105 @@ public class JcrRepository implements Repository { this.anonymousUserContext = anonymousUserContext; } + 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(); @@ -1006,19 +1128,7 @@ public class JcrRepository implements Repository { } if (WORKSPACES_SHARE_SYSTEM_BRANCH) { - assert this.federatedSource != null; - assert this.systemSourceProjection != null; - synchronized (this.federatedSource) { - if (!this.federatedSource.hasWorkspace(workspaceName)) { - // 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); - } - } + 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) Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (revision 1904) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (working copy) @@ -477,7 +477,9 @@ class JcrSession implements Session { || hasRole(ModeShapeRoles.READWRITE, workspaceName) || hasRole(ModeShapeRoles.ADMIN, workspaceName); } else if (ModeShapePermissions.REGISTER_NAMESPACE.equals(action) - || ModeShapePermissions.REGISTER_TYPE.equals(action) || ModeShapePermissions.UNLOCK_ANY.equals(action)) { + || ModeShapePermissions.REGISTER_TYPE.equals(action) || ModeShapePermissions.UNLOCK_ANY.equals(action) + || ModeShapePermissions.CREATE_WORKSPACE.equals(action) + || ModeShapePermissions.DELETE_WORKSPACE.equals(action)) { hasPermission &= hasRole(ModeShapeRoles.ADMIN, workspaceName); } else { hasPermission &= hasRole(ModeShapeRoles.ADMIN, workspaceName) || hasRole(ModeShapeRoles.READWRITE, workspaceName); Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (revision 1904) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (working copy) @@ -749,25 +749,91 @@ class JcrWorkspace implements Workspace { versionManager().restore(versions, removeExisting); } + /** + * {@inheritDoc} + *

+ * The caller must have {@link ModeShapePermissions#CREATE_WORKSPACE permission to create workspaces}, and must have + * {@link ModeShapePermissions#READ read permission} on the source workspace. + *

+ * + * @see javax.jcr.Workspace#createWorkspace(java.lang.String, java.lang.String) + */ @Override public void createWorkspace( String name, String srcWorkspace ) throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException { - throw new UnsupportedRepositoryOperationException(); + CheckArg.isNotNull(name, "name"); + CheckArg.isNotNull(srcWorkspace, "srcWorkspace"); + session.checkLive(); + try { + session.checkPermission(srcWorkspace, null, ModeShapePermissions.READ); + session.checkPermission(name, null, ModeShapePermissions.CREATE_WORKSPACE); + repository.createWorkspace(name, srcWorkspace); + } catch (AccessControlException e) { + throw new AccessDeniedException(e); + } catch (InvalidWorkspaceException e) { + throw new NoSuchWorkspaceException(e); + } catch (RepositorySourceException e) { + throw new RepositoryException(e); + } } + /** + * {@inheritDoc} + *

+ * The caller must have {@link ModeShapePermissions#CREATE_WORKSPACE permission to create workspaces}. + *

+ * + * @see javax.jcr.Workspace#createWorkspace(java.lang.String) + */ @Override public void createWorkspace( String name ) throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException { - throw new UnsupportedRepositoryOperationException(); + CheckArg.isNotNull(name, "name"); + session.checkLive(); + try { + session.checkPermission(name, null, ModeShapePermissions.CREATE_WORKSPACE); + repository.createWorkspace(name, null); + } catch (AccessControlException e) { + throw new AccessDeniedException(e); + } catch (InvalidWorkspaceException e) { + throw new RepositoryException(e); + } catch (RepositorySourceException e) { + throw new RepositoryException(e); + } } + /** + * {@inheritDoc} + *

+ * It is not possible to delete the current workspace. Also, the caller must have + * {@link ModeShapePermissions#DELETE_WORKSPACE permission to delete workspaces}. + *

+ * + * @see javax.jcr.Workspace#deleteWorkspace(java.lang.String) + */ @Override public void deleteWorkspace( String name ) throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException { - throw new UnsupportedRepositoryOperationException(); + CheckArg.isNotNull(name, "name"); + session.checkLive(); + try { + session.checkPermission(name, null, ModeShapePermissions.DELETE_WORKSPACE); + repository.destroyWorkspace(name, this); + } catch (AccessControlException e) { + throw new AccessDeniedException(e); + } catch (InvalidWorkspaceException e) { + throw new RepositoryException(e); + } catch (RepositorySourceException e) { + throw new RepositoryException(e); + } } + /** + * {@inheritDoc} + * + * @see javax.jcr.Workspace#getVersionManager() + */ @Override public VersionManager getVersionManager() { return versionManager; Index: modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapePermissions.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapePermissions.java (revision 1904) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapePermissions.java (working copy) @@ -24,6 +24,7 @@ package org.modeshape.jcr; import javax.jcr.Session; +import javax.jcr.Workspace; /** * The set of constants that can be used for action literals with the {@link Session#checkPermission(String, String)} method. @@ -87,4 +88,16 @@ public interface ModeShapePermissions { */ public static final String READ = "read"; + /** + * The {@link #CREATE_WORKSPACE create_workspace} permission allows the user the ability to + * {@link Workspace#createWorkspace(String) create new workspaces}. + */ + public static final String CREATE_WORKSPACE = "create_workspace"; + + /** + * The {@link #DELETE_WORKSPACE delete_workspace} permission allows the user the ability to + * {@link Workspace#deleteWorkspace(String) delete workspaces}. + */ + public static final String DELETE_WORKSPACE = "delete_workspace"; + } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapeRoles.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapeRoles.java (revision 1904) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapeRoles.java (working copy) @@ -87,6 +87,18 @@ import javax.security.auth.Subject; * * Allows * + * + * create_workspace + * + * + * Allows + * + * + * delete_workspace + * + * + * Allows + * * *

*/ Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (revision 1904) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (working copy) @@ -33,6 +33,7 @@ import java.security.PrivilegedExceptionAction; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.UUID; import javax.jcr.Credentials; import javax.jcr.Repository; @@ -55,6 +56,7 @@ import org.modeshape.graph.Node; import org.modeshape.graph.JaasSecurityContext.UserPasswordCallbackHandler; import org.modeshape.graph.connector.RepositoryConnection; import org.modeshape.graph.connector.RepositoryConnectionFactory; +import org.modeshape.graph.connector.RepositorySource; import org.modeshape.graph.connector.RepositorySourceException; import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.observe.MockObservable; @@ -76,7 +78,7 @@ public class JcrRepositoryTest { private JcrSession session; @BeforeClass - public static void beforeClass() { + public static void beforeAll() { // Initialize IDTrust String configFile = "security/jaas.conf.xml"; IDTrustConfiguration idtrustConfig = new IDTrustConfiguration(); @@ -89,7 +91,7 @@ public class JcrRepositoryTest { } @Before - public void before() throws Exception { + public void beforeEach() throws Exception { MockitoAnnotations.initMocks(this); sourceName = "repository"; @@ -108,9 +110,8 @@ public class JcrRepositoryTest { * * @see org.modeshape.graph.connector.RepositoryConnectionFactory#createConnection(java.lang.String) */ - @SuppressWarnings( "synthetic-access" ) public RepositoryConnection createConnection( String sourceName ) throws RepositorySourceException { - return sourceName.equals(sourceName) ? source.getConnection() : null; + return sourceName.equals(source().getName()) ? source().getConnection() : null; } }; @@ -119,7 +120,7 @@ public class JcrRepositoryTest { repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null); // Set up the graph that goes directly to the source ... - sourceGraph = Graph.create(source, context); + sourceGraph = Graph.create(source(), context); // Set up the graph that goes directly to the system source ... systemGraph = repository.createSystemGraph(context); @@ -136,6 +137,10 @@ public class JcrRepositoryTest { } } + protected RepositorySource source() { + return source; + } + @Test public void shouldFailIfWorkspacesSharingSystemBranchConstantIsFalse() { // Check that the debugging flag is ALWAYS set to true... @@ -577,4 +582,48 @@ public class JcrRepositoryTest { assertThat(readerNode.isLocked(), is(false)); } + @Test + public void shouldHaveAvailableWorkspacesMatchingThoseInSourceContainingJustDefaultWorkspace() throws Exception { + // Set up the source ... + source = new InMemoryRepositorySource(); + source.setName(sourceName); + sourceGraph = Graph.create(source(), context); + + // Create the repository ... + repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null); + + // Get the available workspaces ... + session = createSession(); + Set jcrWorkspaces = setOf(session.getWorkspace().getAccessibleWorkspaceNames()); + + // Check against the session ... + Set graphWorkspaces = sourceGraph.getWorkspaces(); + assertThat(jcrWorkspaces, is(graphWorkspaces)); + } + + @Test + public void shouldHaveAvailableWorkspacesMatchingThoseInSourceContainingPredefinedWorkspaces() throws Exception { + // Set up the source ... + source = new InMemoryRepositorySource(); + source.setName(sourceName); + source.setPredefinedWorkspaceNames(new String[] {"ws1", "ws2", "ws3"}); + source.setDefaultWorkspaceName("ws1"); + sourceGraph = Graph.create(source(), context); + + // Create the repository ... + repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null); + + // Get the available workspaces ... + session = createSession(); + Set jcrWorkspaces = setOf(session.getWorkspace().getAccessibleWorkspaceNames()); + + // Check against the session ... + Set graphWorkspaces = sourceGraph.getWorkspaces(); + assertThat(jcrWorkspaces, is(graphWorkspaces)); + } + + protected Set setOf( T... values ) { + return org.modeshape.common.collection.Collections.unmodifiableSet(values); + } + }