Author: rhauch
Date: 2009-07-22 13:32:10 -0400 (Wed, 22 Jul 2009)
New Revision: 1124
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/federation/Projection.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java
trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java
Log:
DNA-392 All sessions in a repository should share the same /jcr:system content
Changed the JcrRepository implementation to have a new option that defines the source name
(and optionally workspace name) where the /jcr:system branch is to be found. If this
option is not used, or if the supplied names do not correspond to a valid workspace in an
existing source, a transient in-memory source will be used. The existing JcrConfiguration
and JcrEngine are already able to process any options, so they do not need to be changed
to allow a configuration to define the /jcr:system branch location.
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/federation/Projection.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/federation/Projection.java 2009-07-21
20:53:05 UTC (rev 1123)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/federation/Projection.java 2009-07-22
17:32:10 UTC (rev 1124)
@@ -475,8 +475,10 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append(this.workspaceName);
- sb.append('@');
+ if (this.workspaceName != null) {
+ sb.append(this.workspaceName);
+ sb.append('@');
+ }
sb.append(this.sourceName);
sb.append(" { ");
boolean first = true;
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-07-21 20:53:05 UTC
(rev 1123)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-07-22 17:32:10 UTC
(rev 1124)
@@ -99,6 +99,10 @@
public static I18n typeNotFound;
public static I18n supertypeNotFound;
+ public static I18n systemSourceNameOptionValueDoesNotReferenceExistingSource;
+ public static I18n systemSourceNameOptionValueDoesNotReferenceValidWorkspace;
+ public static I18n systemSourceNameOptionValueIsNotFormattedCorrectly;
+
// Used in AbstractJcrNode#getAncestor
public static I18n noNegativeDepth;
public static I18n tooDeep;
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java 2009-07-21 20:53:05
UTC (rev 1123)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java 2009-07-22 17:32:10
UTC (rev 1124)
@@ -45,9 +45,12 @@
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
+import net.jcip.annotations.Immutable;
import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.i18n.I18n;
import org.jboss.dna.common.text.Inflector;
import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.Logger;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.Graph;
import org.jboss.dna.graph.JaasSecurityContext;
@@ -115,9 +118,23 @@
* The {@link Configuration#getAppConfigurationEntry(String) JAAS application
configuration name} that specifies which
* login modules should be used to validate credentials.
*/
- JAAS_LOGIN_CONFIG_NAME;
+ 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 "<code>name of workspace@name of
source</code>", or simply "<code>name of source</code>"
if the default
+ * workspace is to be used. If this option is not used, a transient in-memory
source will be used.
+ * <p>
+ * Note that all leading and trailing whitespace is removed for both the source
name and workspace name. Thus, a value of
+ * "<code>@</code>" implies a zero-length workspace name
and zero-length source name.
+ * </p>
+ * <p>
+ * Also, any use of the '@' character in source and workspace names must
be escaped with a preceding backslash.
+ * </p>
+ */
+ SYSTEM_SOURCE_NAME;
+
+ /**
* 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.,
@@ -180,7 +197,8 @@
private final RepositoryConnectionFactory connectionFactory;
private final RepositoryNodeTypeManager repositoryTypeManager;
private final Map<Option, String> options;
- private final RepositorySource systemSource;
+ private final String systemSourceName;
+ private final String systemWorkspaceName;
private final Projection systemSourceProjection;
private final FederatedRepositorySource federatedSource;
private final Observer observer;
@@ -268,20 +286,63 @@
// Initialize the observer, which receives events from all repository sources
...
this.observer = new RepositoryObserver();
- // 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".)
- InMemoryRepositorySource systemSource = new InMemoryRepositorySource();
- String systemWorkspaceName = "jcr:system";
- String systemSourceName = "jcr:system source";
- systemSource.setName(systemSourceName);
- systemSource.setDefaultWorkspaceName(systemWorkspaceName);
- this.systemSource = systemSource;
- this.connectionFactory = new ConnectionFactoryWithSystem(connectionFactory,
this.systemSource);
+ // 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.getLogger(getClass()).warn(msg, systemSourceNameValue,
systemSourceName);
+ }
+ } catch (InvalidWorkspaceException e) {
+ // Bad workspace name ...
+ systemSourceName = null;
+ I18n msg =
JcrI18n.systemSourceNameOptionValueDoesNotReferenceValidWorkspace;
+ Logger.getLogger(getClass()).warn(msg, systemSourceNameValue,
systemSourceName);
+ } catch (IllegalArgumentException e) {
+ // Invalid format ...
+ systemSourceName = null;
+ I18n msg = JcrI18n.systemSourceNameOptionValueIsNotFormattedCorrectly;
+ Logger.getLogger(getClass()).warn(msg, systemSourceNameValue);
+ }
+ }
+ 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";
+ InMemoryRepositorySource transientSystemSource = new
InMemoryRepositorySource();
+ transientSystemSource.setName(systemSourceName);
+ transientSystemSource.setDefaultWorkspaceName(systemWorkspaceName);
+ connectionFactoryWithSystem = new
ConnectionFactoryWithSystem(connectionFactory, transientSystemSource);
+ }
+ this.systemWorkspaceName = systemWorkspaceName;
+ this.systemSourceName = systemSourceName;
+ this.connectionFactory = connectionFactoryWithSystem;
+ assert this.systemSourceName != null;
+ assert this.connectionFactory != null;
// Set up the "/jcr:system" branch ...
- Graph systemGraph = Graph.create(this.systemSource, executionContext);
+ Graph systemGraph = Graph.create(this.systemSourceName, this.connectionFactory,
executionContext);
systemGraph.useWorkspace(systemWorkspaceName);
initializeSystemContent(systemGraph);
this.sourceName = repositorySourceName;
@@ -353,8 +414,15 @@
}
Graph createSystemGraph() {
+ assert this.systemSourceName != null;
+ assert this.connectionFactory != null;
+ assert this.executionContext != null;
// The default workspace should be the system workspace ...
- return Graph.create(this.systemSource, this.executionContext);
+ Graph result = Graph.create(this.systemSourceName, this.connectionFactory,
this.executionContext);
+ if (this.systemWorkspaceName != null) {
+ result.useWorkspace(systemWorkspaceName);
+ }
+ return result;
}
/**
@@ -462,7 +530,8 @@
* <li>provides neither a <code>getLoginContext()</code>
nor a <code>getAccessControlContext()</code> method and is
* not an instance of {@code SimpleCredentials}.</li>
* <li>provides a <code>getLoginContext()</code> method
that doesn't return a {@link LoginContext}.
- * <li>provides a <code>getLoginContext()</code> method
that returns a <code>null</code> {@link LoginContext}.
+ * <li>provides a <code>getLoginContext()</code> method
that returns a <code>
+ * null</code> {@link LoginContext}.
* <li>does not provide a <code>getLoginContext()</code>
method, but provides a <code>getAccessControlContext()</code>
* method that doesn't return an {@link AccessControlContext}.
* <li>does not provide a <code>getLoginContext()</code>
method, but provides a <code>getAccessControlContext()</code>
@@ -662,6 +731,72 @@
}
}
+ @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 RepositoryObserver implements Observer {
/**
* {@inheritDoc}
Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-07-21
20:53:05 UTC (rev 1123)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-07-22
17:32:10 UTC (rev 1124)
@@ -97,6 +97,10 @@
typeNotFound=No type exists with name "{0}"
supertypeNotFound=Could not find type "{0}" which is a required supertype of
type "{1}"
+systemSourceNameOptionValueDoesNotReferenceExistingSource = The JCR Repository
'SYSTEM_SOURCE_NAME' option value "{0}" references an invalid or
non-existant source "{1}"
+systemSourceNameOptionValueDoesNotReferenceValidWorkspace = The JCR Repository
'SYSTEM_SOURCE_NAME' option value "{0}" references an invalid or
non-existant workspace in the "{1}" source
+systemSourceNameOptionValueIsNotFormattedCorrectly = The JCR Repository
'SYSTEM_SOURCE_NAME' option value "{0}" is invalid or improperly
formatted
+
noNegativeDepth=Depth parameter ({0}) cannot be negative
tooDeep=Depth parameter ({0}) cannot be greater than the result of getDepth() for this
node
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java 2009-07-21
20:53:05 UTC (rev 1123)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java 2009-07-22
17:32:10 UTC (rev 1124)
@@ -350,6 +350,47 @@
assertThat(session.getNamespaceURI("xmlns"),
is("http://www.w3.org/2000/xmlns/"));
}
+ @Test
+ public void shouldParseSourceNameOptionWithOnlySourceName() {
+ assertSourceWorkspacePair("source name", "source name",
null);
+ assertSourceWorkspacePair(" \t source name \n ", "source
name", null);
+ assertSourceWorkspacePair(" \t source \\@ name \n ", "source @
name", null);
+ }
+
+ @Test
+ public void shouldParseSourceNameOptionWithWorkspaceNameAndSourceName() {
+ assertSourceWorkspacePair("workspace@source", "source",
"workspace");
+ assertSourceWorkspacePair(" \t workspace\t@ \t source \t \n",
"source", "workspace");
+ assertSourceWorkspacePair(" \t workspace\\@ name \t@ \t source\\@name \t
\n", "source@name", "workspace@ name");
+ assertSourceWorkspacePair("@ \t source \\@ name \n ", "source @
name", "");
+ assertSourceWorkspacePair(" @ \t source \\@ name \n ", "source @
name", "");
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldFailToParseSourceNameOptionThatHasZeroLengthSource() {
+ new JcrRepository.SourceWorkspacePair("workspace@");
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldFailToParseSourceNameOptionThatHasBlankSource() {
+ new JcrRepository.SourceWorkspacePair("workspace@ ");
+ }
+
+ @Test
+ public void shouldParseSourceNameOptionThatHasBlankSourceAndWorkspace() {
+ assertSourceWorkspacePair("@", "", "");
+ assertSourceWorkspacePair(" @ ", "", "");
+ }
+
+ protected void assertSourceWorkspacePair( String value,
+ String expectedSourceName,
+ String expectedWorkspaceName ) {
+ JcrRepository.SourceWorkspacePair pair = new
JcrRepository.SourceWorkspacePair(value);
+ assertThat(pair, is(notNullValue()));
+ assertThat(pair.getSourceName(), is(expectedSourceName));
+ assertThat(pair.getWorkspaceName(), is(expectedWorkspaceName));
+ }
+
protected JcrSession createSession() throws Exception {
LoginContext login = new LoginContext("dna-jcr", new
UserPasswordCallbackHandler("superuser", "superuser".toCharArray()));
login.login();