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);
+ }
+
}