Index: deploy/jbossas/modeshape-jbossas-service/src/kit/modeshape-config.xml
===================================================================
--- deploy/jbossas/modeshape-jbossas-service/src/kit/modeshape-config.xml (revision 2462)
+++ deploy/jbossas/modeshape-jbossas-service/src/kit/modeshape-config.xml (working copy)
@@ -25,145 +25,149 @@
~ Boston, MA 02110-1301 USA
-->
-
+
+
+
+
+
+ store
+
+
+
+
+
+
+ /org/modeshape/sequencer/teiid/teiid.cnd
+ /org/modeshape/connector/meta/jdbc/connector-metajdbc.cnd
+ /org/modeshape/sequencer/classfile/sequencer-classfile.cnd
+ /org/modeshape/sequencer/image/images.cnd
+ /org/modeshape/sequencer/java/javaSource.cnd
+ /org/modeshape/sequencer/mp3/mp3.cnd
+ /org/modeshape/sequencer/msoffice/msoffice.cnd
+ /org/modeshape/sequencer/text/sequencer-text.cnd
+ /org/modeshape/sequencer/xml/xml.cnd
+ /org/modeshape/sequencer/zip/zip.cnd
+ /org/modeshape/sequencer/ddl/StandardDdl.cnd
+ /org/modeshape/sequencer/ddl/dialect/derby/DerbyDdl.cnd
+ /org/modeshape/sequencer/ddl/dialect/oracle/OracleDdl.cnd
+ /org/modeshape/sequencer/ddl/dialect/postgres/PostgresDdl.cnd
+
+
+
+
+
-
+
-
+
- Sequences *.csv text files loaded into the repository under '/files', extracting comma-separated and delimited files into columnar information.
- /files//(*.csv[*])/jcr:content[@jcr:data] => /sequenced/text/delimited/$1
-
- ,
+ Sequences *.csv text files loaded under '/files', extracting comma-separated and delimited files into columnar information.
+ store:default:/files//(*.csv[*])/jcr:content[@jcr:data] => store:default:/sequenced/text/delimited/$1
+
+ ,
- Sequences *.txt fixed-width text files loaded into the repository under '/files', extracting splitting rows into columns based on predefined positions.
- /files//(*.txt[*])/jcr:content[@jcr:data] => /sequenced/text/fixedWidth/$1
-
-
+ Sequences *.txt fixed-width text files loaded under '/files', extracting splitting rows into columns based on predefined positions.
+ store:default:/files//(*.txt[*])/jcr:content[@jcr:data] => store:default:/sequenced/text/fixedWidth/$1
+
+
- Sequences Teiid relational models (e.g., *.xmi) loaded into the repository under '/files', extracting the structure defined in the models.
-
- /files(//)(*.xmi[*])/jcr:content[@jcr:data] => /sequenced/teiid/models$1
+ store:default:/files(//)(*.xmi[*])/jcr:content[@jcr:data] => store:default:/sequenced/teiid/models$1
- Sequences Teiid Virtual Databases (e.g., *.vdb) loaded into the repository under '/files', extracting the VDB metadata and the structure defined in the VDB's relational models.
-
- /files(//)(*.vdb[*])/jcr:content[@jcr:data] => /sequenced/teiid/vdbs$1
+ store:default:/files(//)(*.vdb[*])/jcr:content[@jcr:data] => store:default:/sequenced/teiid/vdbs$1
- Sequences Java class files loaded into the repository under '/files', extracting structural information.
-
- /files(//(*.class[*]))/jcr:content[@jcr:data] => /sequenced/class/$1
+ store:default:/files(//(*.class[*]))/jcr:content[@jcr:data] => store:default:/sequenced/class/$1
- Sequences Java source files loaded into the repository under '/files', extracting structural information.
-
- /files(//(*.java[*]))/jcr:content[@jcr:data] => /sequenced/java/$1
+ store:default:/files(//(*.java[*]))/jcr:content[@jcr:data] => store:default:/sequenced/java/$1
- Sequences CND files loaded into the repository under '/files', extracting the contained node type definitions.
-
- /files(//(*.cnd[*]))/jcr:content[@jcr:data] => /sequenced/cnd/$1
+ store:default:/files(//(*.cnd[*]))/jcr:content[@jcr:data] => store:default:/sequenced/cnd/$1
- Sequences DDL files loaded into the repository under '/files', extracting the structured abstract syntax tree of the DDL commands and expressions.
-
- /files(//(*.ddl[*]))/jcr:content[@jcr:data] => /sequenced/ddl/$1
+ store:default:/files(//(*.ddl[*]))/jcr:content[@jcr:data] => store:default:/sequenced/ddl/$1
Sequences Microsoft Office documents and presentations under '/files', extracting summary information and structure.
- /files//(*.(xls|ppt|doc)[*])/jcr:content[@jcr:data] => /sequenced/msoffice/$1
+ store:default:/files//(*.(xls|ppt|doc)[*])/jcr:content[@jcr:data] => store:default:/sequenced/msoffice/$1
- Sequences XML files loaded into the repository under '/files', extracting the contents into the equivalent JCR graph structure.
-
- /files(//(*.xml[*]))/jcr:content[@jcr:data] => /sequenced/xml/$1
+ store:default:/files(//(*.xml[*]))/jcr:content[@jcr:data] => store:default:/sequenced/xml/$1
- Sequences ZIP files loaded into the repository under '/files', extracting the archive file contents into the equivalent JCR graph structure of 'nt:file' and 'nt:folder' nodes.
-
- /files(//)(*.zip[*])/jcr:content[@jcr:data] => /sequenced/zip/$1
+ store:default:/files(//)(*.zip[*])/jcr:content[@jcr:data] => store:default:/sequenced/zip/$1
-
-
-
-
-
- repository
-
-
-
-
-
-
- /org/modeshape/sequencer/teiid/teiid.cnd
- /org/modeshape/connector/meta/jdbc/connector-metajdbc.cnd
- /org/modeshape/sequencer/classfile/sequencer-classfile.cnd
- /org/modeshape/sequencer/image/images.cnd
- /org/modeshape/sequencer/java/javaSource.cnd
- /org/modeshape/sequencer/mp3/mp3.cnd
- /org/modeshape/sequencer/msoffice/msoffice.cnd
- /org/modeshape/sequencer/text/sequencer-text.cnd
- /org/modeshape/sequencer/xml/xml.cnd
- /org/modeshape/sequencer/zip/zip.cnd
- /org/modeshape/sequencer/ddl/StandardDdl.cnd
- /org/modeshape/sequencer/ddl/dialect/derby/DerbyDdl.cnd
- /org/modeshape/sequencer/ddl/dialect/oracle/OracleDdl.cnd
- /org/modeshape/sequencer/ddl/dialect/postgres/PostgresDdl.cnd
-
-
-
\ No newline at end of file
Index: docs/reference/src/main/docbook/en-US/content/core/sequencing.xml
===================================================================
--- docs/reference/src/main/docbook/en-US/content/core/sequencing.xml (revision 2462)
+++ docs/reference/src/main/docbook/en-US/content/core/sequencing.xml (working copy)
@@ -253,6 +253,46 @@ public interface &SequencerOutput; {
Square brackets can also be used to specify criteria on a node's properties or children. Whatever appears in between the square
brackets does not appear in the selected node.
+
+ So far, we've talked about how input paths and output paths are independent of the repository and workspace. However, there are times
+ when it's desirable to configure sequencers to only work against content in a specific source and/or specific workspace.
+ In these cases, it is possible to specify the repository name and workspace names before the path. For example:
+
+
+ Input Paths with Source and Workspace Names
+
+
+
+
+
+ Input Path
+ Description
+
+
+
+ source:default:/a/(b|c|d)Match nodes in the "default
" workspace within the "source
"
+ source that are children of the top level node "a
" and named "b
",
+ "c
" or "d
". None of the nodes may have same-name-sibling indexes.
+ :default:/a/(b|c|d)Match nodes in the "default
" workspace within any source
+ source that are children of the top level node "a
" and named "b
",
+ "c
" or "d
". None of the nodes may have same-name-sibling indexes.
+ source::/a/(b|c|d)Match nodes in any workspace in the "source
" source
+ that are children of the top level node "a
" and named "b
",
+ "c
" or "d
". None of the nodes may have same-name-sibling indexes.
+ ::/a/(b|c|d)Match nodes in any within any source
+ source that are children of the top level node "a
" and named "b
",
+ "c
" or "d
". None of the nodes may have same-name-sibling indexes. (This is equivalent to
+ the path "/a/(b|c|d)
".)
+
+
+
+
+ Again, the rules are pretty straightforward. You can leave off the repository name and workspace name, or you can prepend the path
+ with "{sourceNamePattern}:{workspaceNamePattern}:
", where "{sourceNamePattern}
is a regular-expression
+ pattern used to match the applicable source names, and "{workspaceNamePattern}
is a regular-expression
+ pattern used to match the applicable workspace names. A blank pattern implies any match, and is a shorthand notation for ".*
".
+ Note that the repository names may not include forward slashes (e.g., '/') or colons (e.g., ':').
+
Let's go back to the previous code fragment and look at the first path expression:
Index: modeshape-graph/src/main/java/org/modeshape/graph/property/PathExpression.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/property/PathExpression.java (revision 2462)
+++ modeshape-graph/src/main/java/org/modeshape/graph/property/PathExpression.java (working copy)
@@ -29,29 +29,18 @@ import java.util.regex.PatternSyntaxException;
import net.jcip.annotations.Immutable;
import org.modeshape.common.util.CheckArg;
import org.modeshape.common.util.HashCode;
+import org.modeshape.common.util.ObjectUtil;
import org.modeshape.graph.GraphI18n;
/**
* An expression that defines an acceptable path using a regular-expression-like language. Path expressions can be used to
* represent node paths or properties.
*
- * Path expressions consist of two parts: a selection criteria (or an input path) and an output path:
- *
- *
- *
- * inputPath => outputPath
- *
- *
- * The inputPath part defines an expression for the path of a node that is to be sequenced. Input paths consist of '
- * /
' separated segments, where each segment represents a pattern for a single node's name (including the
- * same-name-sibling indexes) and '@
' signifies a property name.
- *
- *
- * Let's first look at some simple examples:
+ * Let's first look at some simple examples of path expressions:
*
*
*
- * Input Path |
+ * Path expression |
* Description |
*
*
@@ -105,12 +94,12 @@ import org.modeshape.graph.GraphI18n;
* slash characters are treated as two.
*
*
- * Many input paths can be created using just these simple rules. However, input paths can be more complicated. Here are some more
- * examples:
+ * Many path expressions can be created using just these simple rules. However, input paths can be more complicated. Here are some
+ * more examples:
*
*
*
- * Input Path |
+ * Path expressions |
* Description |
*
*
@@ -142,6 +131,12 @@ import org.modeshape.graph.GraphI18n;
* Square brackets can also be used to specify criteria on a node's properties or children. Whatever appears in between the square
* brackets does not appear in the selected node.
*
+ * Repository and Workspace names
+ *
+ * Path expressions can also specify restrictions on the repository name and workspace name, to constrain the path expression to
+ * matching only paths from workspaces in repositories meeting the name criteria. Of course, if the path expression doesn't
+ * include these restrictions, the repository and workspace names are not considered when matching paths.
+ *
*/
@Immutable
public class PathExpression implements Serializable {
@@ -189,8 +184,35 @@ public class PathExpression implements Serializable {
private static final String NON_INDEX_PREDICATE_PATTERN_STRING = "\\[(?:(?:\\d+(?:,\\d+)*)|\\*)\\]|(\\[[^\\]]+\\])";
private static final Pattern NON_INDEX_PREDICATE_PATTERN = Pattern.compile(NON_INDEX_PREDICATE_PATTERN_STRING);
+ /**
+ * The regular expression that is used to extract the repository name, workspace name, and path from an path expression (or a
+ * real path). The regular expression is ((([^:/]*):)?(([^:/]*):))?(.*)
. Group 3 will contain the repository
+ * name, group 5 the workspace name, and group 6 the path.
+ */
+ private static final String REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN_STRING = "((([^:/]*):)?(([^:/]*):))?(.*)";
+ private static final Pattern REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN = Pattern.compile(REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN_STRING);
+
private final String expression;
+
+ /**
+ * This is the pattern that is used to determine if the particular path is from a particular repository. This pattern will be
+ * null if the expression does not constrain the repository name.
+ */
+ private final Pattern repositoryPattern;
+
+ /**
+ * This is the pattern that is used to determine if the particular path is from a particular workspace. This pattern will be
+ * null if the expression does not constrain the workspace name.
+ */
+ private final Pattern workspacePattern;
+ /**
+ * This is the pattern that is used to determine if there is a match with particular paths.
+ */
private final Pattern matchPattern;
+ /**
+ * This is the pattern that is used to determine which parts of the particular input paths are included in the
+ * {@link Matcher#getSelectedNodePath() selected path}, only after the input path has already matched.
+ */
private final Pattern selectPattern;
/**
@@ -206,8 +228,23 @@ public class PathExpression implements Serializable {
if (this.expression.length() == 0) {
throw new InvalidPathExpressionException(GraphI18n.pathExpressionMayNotBeBlank.text());
}
+
+ // Separate out the repository name, workspace name, and path fragments into separate match patterns ...
+ RepositoryPath repoPath = parseRepositoryPath(this.expression);
+ if (repoPath == null) {
+ throw new InvalidPathExpressionException(GraphI18n.pathExpressionHasInvalidMatch.text(this.expression,
+ this.expression));
+ }
+ String repoPatternStr = repoPath.repositoryName != null ? repoPath.repositoryName : ".*";
+ String workPatternStr = repoPath.workspaceName != null ? repoPath.workspaceName : ".*";
+ String pathPatternStr = repoPath.path;
+ this.repositoryPattern = Pattern.compile(repoPatternStr);
+ this.workspacePattern = Pattern.compile(workPatternStr);
+
+ // Build the repository match pattern ...
+
// Build the match pattern, which determines whether a path matches the condition ...
- String matchString = this.expression;
+ String matchString = pathPatternStr;
try {
matchString = removeUnusedPredicates(matchString);
matchString = replaceXPathPatterns(matchString);
@@ -217,7 +254,7 @@ public class PathExpression implements Serializable {
throw new InvalidPathExpressionException(msg, e);
}
// Build the select pattern, which determines the path that will be selected ...
- String selectString = this.expression;
+ String selectString = pathPatternStr;
try {
selectString = removeAllPredicatesExceptIndexes(selectString);
selectString = replaceXPathPatterns(selectString);
@@ -400,28 +437,48 @@ public class PathExpression implements Serializable {
}
/**
- * @param absolutePath
- * @return the matcher
+ * Obtain a Matcher that can be used to convert the supplied absolute path (with repository name and workspace name) into an
+ * output repository, and output workspace name, and output path.
+ *
+ * @param absolutePath the path, of the form {repoName}:{workspaceName}:{absPath}
, where
+ * {repoName}:{workspaceName}:
is optional
+ * @return the matcher; never null
*/
public Matcher matcher( String absolutePath ) {
+ // Extra the repository name, workspace name and absPath from the supplied path ...
+ RepositoryPath repoPath = parseRepositoryPath(absolutePath);
+ if (repoPath == null) {
+ // No match, so return immediately ...
+ return new Matcher(null, absolutePath, null, null, null);
+ }
+ String repoName = repoPath.repositoryName != null ? repoPath.repositoryName : "";
+ String workspaceName = repoPath.workspaceName != null ? repoPath.workspaceName : "";
+ String path = repoPath.path;
+
+ // Determine if the input repository matches the repository name pattern ...
+ if (!repositoryPattern.matcher(repoName).matches() || !workspacePattern.matcher(workspaceName).matches()) {
+ // No match, so return immediately ...
+ return new Matcher(null, path, null, null, null);
+ }
+
// Determine if the input path match the select expression ...
- String originalAbsolutePath = absolutePath;
+ String originalAbsolutePath = path;
// if (!absolutePath.endsWith("/")) absolutePath = absolutePath + "/";
// Remove all trailing '/' ...
- absolutePath = absolutePath.replaceAll("/+$", "");
+ path = path.replaceAll("/+$", "");
// See if the supplied absolute path matches the pattern ...
- final java.util.regex.Matcher matcher = this.matchPattern.matcher(absolutePath);
+ final java.util.regex.Matcher matcher = this.matchPattern.matcher(path);
if (!matcher.matches()) {
// No match, so return immediately ...
- return new Matcher(matcher, originalAbsolutePath, null);
+ return new Matcher(matcher, originalAbsolutePath, null, null, null);
}
// The absolute path does match the pattern, so use the select pattern and try to grab the selected path ...
- final java.util.regex.Matcher selectMatcher = this.selectPattern.matcher(absolutePath);
+ final java.util.regex.Matcher selectMatcher = this.selectPattern.matcher(path);
if (!selectMatcher.matches()) {
// Nothing can be selected, so return immediately ...
- return new Matcher(matcher, null, null);
+ return new Matcher(matcher, null, null, null, null);
}
// Grab the selected path ...
String selectedPath = selectMatcher.group(1);
@@ -429,28 +486,34 @@ public class PathExpression implements Serializable {
// Remove the trailing '/@property' ...
selectedPath = selectedPath.replaceAll("/@[^/\\[\\]]+$", "");
- return new Matcher(matcher, originalAbsolutePath, selectedPath);
+ return new Matcher(matcher, originalAbsolutePath, repoName, workspaceName, selectedPath);
}
@Immutable
public static class Matcher {
private final String inputPath;
+ private final String selectedRepository;
+ private final String selectedWorkspace;
private final String selectedPath;
private final java.util.regex.Matcher inputMatcher;
private final int hc;
protected Matcher( java.util.regex.Matcher inputMatcher,
String inputPath,
+ String selectedRepository,
+ String selectedWorkspace,
String selectedPath ) {
this.inputMatcher = inputMatcher;
this.inputPath = inputPath;
+ this.selectedRepository = selectedRepository == null || selectedRepository.length() == 0 ? null : selectedRepository;
+ this.selectedWorkspace = selectedWorkspace == null || selectedWorkspace.length() == 0 ? null : selectedWorkspace;
this.selectedPath = selectedPath;
this.hc = HashCode.compute(this.inputPath, this.selectedPath);
}
public boolean matches() {
- return this.selectedPath != null;
+ return this.inputMatcher != null && this.selectedPath != null;
}
/**
@@ -467,7 +530,26 @@ public class PathExpression implements Serializable {
return this.selectedPath;
}
+ /**
+ * Get the name of the selected repository.
+ *
+ * @return the repository name, or null if there is none specified
+ */
+ public String getSelectedRepositoryName() {
+ return this.selectedRepository;
+ }
+
+ /**
+ * Get the name of the selected workspace.
+ *
+ * @return the workspace name, or null if there is none specified
+ */
+ public String getSelectedWorkspaceName() {
+ return this.selectedWorkspace;
+ }
+
public int groupCount() {
+ if (this.inputMatcher == null) return 0;
return this.inputMatcher.groupCount();
}
@@ -532,4 +614,90 @@ public class PathExpression implements Serializable {
private static final PathExpression ALL_PATHS_EXPRESSION = PathExpression.compile("//");
+ /**
+ * Parse a path of the form {repoName}:{workspaceName}:{absolutePath}
or {absolutePath}
.
+ *
+ * @param path the path
+ * @return the repository path, or null if the supplied path doesn't match any of the path patterns
+ */
+ public static RepositoryPath parseRepositoryPath( String path ) {
+ // Extra the repository name, workspace name and absPath from the supplied path ...
+ java.util.regex.Matcher pathMatcher = REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN.matcher(path);
+ if (!pathMatcher.matches()) {
+ // No match ...
+ return null;
+ }
+ String repoName = pathMatcher.group(3);
+ String workspaceName = pathMatcher.group(5);
+ String absolutePath = pathMatcher.group(6);
+ if (repoName == null || repoName.length() == 0 || repoName.trim().length() == 0) repoName = null;
+ if (workspaceName == null || workspaceName.length() == 0 || workspaceName.trim().length() == 0) workspaceName = null;
+ return new RepositoryPath(repoName, workspaceName, absolutePath);
+ }
+
+ @Immutable
+ public static class RepositoryPath {
+ public final String repositoryName;
+ public final String workspaceName;
+ public final String path;
+
+ public RepositoryPath( String repositoryName,
+ String workspaceName,
+ String path ) {
+ this.repositoryName = repositoryName;
+ this.workspaceName = workspaceName;
+ this.path = path;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return path.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof RepositoryPath) {
+ RepositoryPath that = (RepositoryPath)obj;
+ if (!ObjectUtil.isEqualWithNulls(this.repositoryName, that.repositoryName)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.workspaceName, that.workspaceName)) return false;
+ return this.path.equals(that.path);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return (repositoryName != null ? repositoryName : "") + ":" + (workspaceName != null ? workspaceName : "") + ":"
+ + path;
+ }
+
+ public RepositoryPath withRepositoryName( String repositoryName ) {
+ return new RepositoryPath(repositoryName, workspaceName, path);
+ }
+
+ public RepositoryPath withWorkspaceName( String workspaceName ) {
+ return new RepositoryPath(repositoryName, workspaceName, path);
+ }
+
+ public RepositoryPath withPath( String path ) {
+ return new RepositoryPath(repositoryName, workspaceName, path);
+ }
+ }
+
}
Index: modeshape-graph/src/test/java/org/modeshape/graph/property/PathExpressionTest.java
===================================================================
--- modeshape-graph/src/test/java/org/modeshape/graph/property/PathExpressionTest.java (revision 2462)
+++ modeshape-graph/src/test/java/org/modeshape/graph/property/PathExpressionTest.java (working copy)
@@ -27,8 +27,6 @@ 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 org.modeshape.graph.property.InvalidPathExpressionException;
-import org.modeshape.graph.property.PathExpression;
import org.junit.Before;
import org.junit.Test;
@@ -107,6 +105,21 @@ public class PathExpressionTest {
}
@Test
+ public void shouldCompileExpressionWithRepositoryAndWorkspaceNames() {
+ assertThat(PathExpression.compile("repo:ws:/a/b/c"), is(notNullValue()));
+ }
+
+ @Test
+ public void shouldCompileExpressionWithRepositoryAndNoWorkspaceNames() {
+ assertThat(PathExpression.compile("repo::/a/b/c"), is(notNullValue()));
+ }
+
+ @Test
+ public void shouldCompileExpressionWithNoRepositoryAndNoWorkspaceNames() {
+ assertThat(PathExpression.compile("::/a/b/c"), is(notNullValue()));
+ }
+
+ @Test
public void shouldNotRemoveUsedPredicates() {
assertThat(expr.removeUnusedPredicates("/a/b/c"), is("/a/b/c"));
assertThat(expr.removeUnusedPredicates("/a/b[0]/c"), is("/a/b[0]/c"));
@@ -478,8 +491,8 @@ public class PathExpressionTest {
@Test
public void shouldMatchExpressionsWithRepositoryInSelectionPath() {
- expr = PathExpression.compile("reposA:/a/b/c[d/e/@something]");
- assertThat(expr.matcher("reposA:/a/b/c/d/e/@something").matches(), is(true));
+ expr = PathExpression.compile("reposA::/a/b/c[d/e/@something]");
+ assertThat(expr.matcher("reposA::/a/b/c/d/e/@something").matches(), is(true));
}
@Test
@@ -584,4 +597,81 @@ public class PathExpressionTest {
expr = PathExpression.compile("//(*.(jpeg|gif|bmp|pcx|png|iff|ras|pbm|pgm|ppm|psd))[*]/jcr:content[@jcr:data]");
assertThat(expr.matcher("/a/b/caution.png/jcr:content/@jcr:data").matches(), is(true));
}
+
+ @Test
+ public void shouldMatchStringWithRepositoryAndWorkspaceUsingExpressionWithoutRepositoryOrWorkspace() {
+ expr = PathExpression.compile("//a/b");
+ assertThat(expr.matcher("repo:workspace:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/y/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace2:/a").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace2:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace2:/x/y/a/b").matches(), is(true));
+ }
+
+ @Test
+ public void shouldMatchStringWithRepositoryAndWorkspaceUsingExpressionWithBlankRepositoryAndBlankWorkspace() {
+ expr = PathExpression.compile(":://a/b");
+ assertThat(expr.matcher("repo:workspace:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/y/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace2:/a").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace2:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace2:/x/y/a/b").matches(), is(true));
+ }
+
+ @Test
+ public void shouldMatchStringWithRepositoryAndWorkspaceUsingExpressionWithRepositoryAndBlankWorkspace() {
+ expr = PathExpression.compile("repo:://a/b");
+ assertThat(expr.matcher("repo:workspace:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/y/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace2:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace2:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace2:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace2:/x/y/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace2:/a").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/x/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/x/y/a/b").matches(), is(false));
+ }
+
+ @Test
+ public void shouldMatchStringWithRepositoryAndWorkspaceUsingExpressionWithRepositoryAndWorkspace() {
+ expr = PathExpression.compile("repo:workspace://a/b");
+ assertThat(expr.matcher("repo:workspace:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/y/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace2:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace2:/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace2:/x/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace2:/x/y/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/a").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/x/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace2:/x/y/a/b").matches(), is(false));
+ }
+
+ @Test
+ public void shouldMatchStringWithRepositoryAndWorkspaceUsingExpressionWithBlankRepositoryAndWorkspace() {
+ expr = PathExpression.compile(":workspace://a/b");
+ assertThat(expr.matcher("repo:workspace:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace:/x/y/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo:workspace2:/a").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace2:/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace2:/x/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo:workspace2:/x/y/a/b").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace:/a").matches(), is(false));
+ assertThat(expr.matcher("repo1:workspace:/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace:/x/a/b").matches(), is(true));
+ assertThat(expr.matcher("repo1:workspace:/x/y/a/b").matches(), is(true));
+ }
}
Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/JavaSequencerIntegrationTest.java
===================================================================
--- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/JavaSequencerIntegrationTest.java (revision 2462)
+++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/JavaSequencerIntegrationTest.java (working copy)
@@ -75,6 +75,7 @@ public class JavaSequencerIntegrationTest extends AbstractSequencerTest {
assertThat(file.exists(), is(true));
uploadFile(file.toURI().toURL(), "/files/");
waitUntilSequencedNodesIs(1);
+ Thread.sleep(200);
// printSubgraph(assertNode("/"));
// Find the sequenced node ...
Index: modeshape-integration-tests/src/test/resources/config/configRepositoryForZipSequencing.xml
===================================================================
--- modeshape-integration-tests/src/test/resources/config/configRepositoryForZipSequencing.xml (revision 2462)
+++ modeshape-integration-tests/src/test/resources/config/configRepositoryForZipSequencing.xml (working copy)
@@ -48,7 +48,7 @@
Sequences ZIP files loaded into the repository under '/files', extracting the archive file contents into the equivalent JCR graph structure of 'nt:file' and 'nt:folder' nodes.
- /files(//)(*.zip[*])/jcr:content[@jcr:data] => /sequenced/zip/$1
+ Store:default:/files(//)(*.zip[*])/jcr:content[@jcr:data] => Store:default:/sequenced/zip/$1
Index: modeshape-repository/src/main/java/org/modeshape/repository/sequencer/SequencerPathExpression.java
===================================================================
--- modeshape-repository/src/main/java/org/modeshape/repository/sequencer/SequencerPathExpression.java (revision 2462)
+++ modeshape-repository/src/main/java/org/modeshape/repository/sequencer/SequencerPathExpression.java (working copy)
@@ -30,7 +30,9 @@ import java.util.regex.Pattern;
import net.jcip.annotations.Immutable;
import org.modeshape.common.util.CheckArg;
import org.modeshape.common.util.HashCode;
+import org.modeshape.common.util.ObjectUtil;
import org.modeshape.graph.property.PathExpression;
+import org.modeshape.graph.property.PathExpression.RepositoryPath;
import org.modeshape.repository.RepositoryI18n;
/**
@@ -152,12 +154,16 @@ public class SequencerPathExpression implements Serializable {
}
/**
- * @param absolutePath
- * @return the matcher
+ * Obtain a Matcher that can be used to convert the supplied absolute path (with repository name and workspace name) into an
+ * output repository, and output workspace name, and output path.
+ *
+ * @param absolutePath the path, of the form {repoName}:{workspaceName}:{absPath}
+ * @return the matcher; never null
*/
public Matcher matcher( String absolutePath ) {
PathExpression.Matcher inputMatcher = selectExpression.matcher(absolutePath);
String outputPath = null;
+ RepositoryPath repoPath = null;
if (inputMatcher.matches()) {
// Grab the named groups ...
Map replacements = new HashMap();
@@ -169,66 +175,72 @@ public class SequencerPathExpression implements Serializable {
String selectedPath = inputMatcher.getSelectedNodePath();
// Find the output path using the groups from the match pattern ...
- outputPath = this.outputExpression;
- if (!DEFAULT_OUTPUT_EXPRESSION.equals(outputPath)) {
- java.util.regex.Matcher replacementMatcher = REPLACEMENT_VARIABLE_PATTERN.matcher(outputPath);
- StringBuffer sb = new StringBuffer();
- if (replacementMatcher.find()) {
- do {
- String variable = replacementMatcher.group(1);
- String replacement = replacements.get(Integer.valueOf(variable));
- if (replacement == null) replacement = replacementMatcher.group(0);
- replacementMatcher.appendReplacement(sb, replacement);
- } while (replacementMatcher.find());
- replacementMatcher.appendTail(sb);
- outputPath = sb.toString();
- }
- // Make sure there is a trailing '/' ...
- if (!outputPath.endsWith("/")) outputPath = outputPath + "/";
-
- // Replace all references to "/./" with "/" ...
- outputPath = outputPath.replaceAll("/\\./", "/");
-
- // Remove any path segment followed by a parent reference ...
- java.util.regex.Matcher parentMatcher = PARENT_PATTERN.matcher(outputPath);
- while (parentMatcher.find()) {
- outputPath = parentMatcher.replaceAll("");
+ repoPath = PathExpression.parseRepositoryPath(this.outputExpression);
+ if (repoPath != null) {
+ if (repoPath.repositoryName == null) repoPath = repoPath.withRepositoryName(inputMatcher.getSelectedRepositoryName());
+ if (repoPath.workspaceName == null) repoPath = repoPath.withWorkspaceName(inputMatcher.getSelectedWorkspaceName());
+ outputPath = repoPath.path;
+ if (!DEFAULT_OUTPUT_EXPRESSION.equals(outputPath)) {
+ java.util.regex.Matcher replacementMatcher = REPLACEMENT_VARIABLE_PATTERN.matcher(outputPath);
+ StringBuffer sb = new StringBuffer();
+ if (replacementMatcher.find()) {
+ do {
+ String variable = replacementMatcher.group(1);
+ String replacement = replacements.get(Integer.valueOf(variable));
+ if (replacement == null) replacement = replacementMatcher.group(0);
+ replacementMatcher.appendReplacement(sb, replacement);
+ } while (replacementMatcher.find());
+ replacementMatcher.appendTail(sb);
+ outputPath = sb.toString();
+ }
// Make sure there is a trailing '/' ...
if (!outputPath.endsWith("/")) outputPath = outputPath + "/";
- parentMatcher = PARENT_PATTERN.matcher(outputPath);
- }
- // Remove all multiple occurrences of '/' ...
- outputPath = outputPath.replaceAll("/{2,}", "/");
+ // Replace all references to "/./" with "/" ...
+ outputPath = outputPath.replaceAll("/\\./", "/");
- // Remove the trailing '/@property' ...
- outputPath = outputPath.replaceAll("/@[^/\\[\\]]+$", "");
+ // Remove any path segment followed by a parent reference ...
+ java.util.regex.Matcher parentMatcher = PARENT_PATTERN.matcher(outputPath);
+ while (parentMatcher.find()) {
+ outputPath = parentMatcher.replaceAll("");
+ // Make sure there is a trailing '/' ...
+ if (!outputPath.endsWith("/")) outputPath = outputPath + "/";
+ parentMatcher = PARENT_PATTERN.matcher(outputPath);
+ }
- // Remove a trailing '/' ...
- outputPath = outputPath.replaceAll("/$", "");
+ // Remove all multiple occurrences of '/' ...
+ outputPath = outputPath.replaceAll("/{2,}", "/");
- // If the output path is blank, then use the default output expression ...
- if (outputPath.length() == 0) outputPath = DEFAULT_OUTPUT_EXPRESSION;
+ // Remove the trailing '/@property' ...
+ outputPath = outputPath.replaceAll("/@[^/\\[\\]]+$", "");
- }
- if (DEFAULT_OUTPUT_EXPRESSION.equals(outputPath)) {
- // The output path is the default expression, so use the selected path ...
- outputPath = selectedPath;
+ // Remove a trailing '/' ...
+ outputPath = outputPath.replaceAll("/$", "");
+
+ // If the output path is blank, then use the default output expression ...
+ if (outputPath.length() == 0) outputPath = DEFAULT_OUTPUT_EXPRESSION;
+
+ }
+ if (DEFAULT_OUTPUT_EXPRESSION.equals(outputPath)) {
+ // The output path is the default expression, so use the selected path ...
+ outputPath = selectedPath;
+ }
+ repoPath = repoPath.withPath(outputPath);
}
}
- return new Matcher(inputMatcher, outputPath);
+ return new Matcher(inputMatcher, repoPath);
}
@Immutable
public static class Matcher {
private final PathExpression.Matcher inputMatcher;
- private final String outputPath;
+ private final RepositoryPath outputPath;
private final int hc;
protected Matcher( PathExpression.Matcher inputMatcher,
- String outputPath ) {
+ RepositoryPath outputPath ) {
this.inputMatcher = inputMatcher;
this.outputPath = outputPath;
this.hc = HashCode.compute(super.hashCode(), this.outputPath);
@@ -253,10 +265,30 @@ public class SequencerPathExpression implements Serializable {
}
/**
- * @return outputPath
+ * Get the path in the repository where the sequenced content should be placed.
+ *
+ * @return outputPath the output path, or null if this matcher does not match the input
*/
public String getOutputPath() {
- return this.outputPath;
+ return this.outputPath != null ? this.outputPath.path : null;
+ }
+
+ /**
+ * Get the name of the repository where the sequenced content should be placed.
+ *
+ * @return outputPath the output path, or null if this matcher does not match the input
+ */
+ public String getOutputRepositoryName() {
+ return this.outputPath != null ? this.outputPath.repositoryName : null;
+ }
+
+ /**
+ * Get the name of the workspace where the sequenced content should be placed.
+ *
+ * @return outputPath the output path, or null if this matcher does not match the input
+ */
+ public String getOutputWorkspaceName() {
+ return this.outputPath != null ? this.outputPath.workspaceName : null;
}
/**
@@ -276,8 +308,7 @@ public class SequencerPathExpression implements Serializable {
if (obj instanceof SequencerPathExpression.Matcher) {
SequencerPathExpression.Matcher that = (SequencerPathExpression.Matcher)obj;
if (!super.equals(that)) return false;
- if (!this.outputPath.equalsIgnoreCase(that.outputPath)) return false;
- return true;
+ return ObjectUtil.isEqualWithNulls(this.outputPath, that.outputPath);
}
return false;
}
Index: modeshape-repository/src/main/java/org/modeshape/repository/sequencer/SequencingService.java
===================================================================
--- modeshape-repository/src/main/java/org/modeshape/repository/sequencer/SequencingService.java (revision 2462)
+++ modeshape-repository/src/main/java/org/modeshape/repository/sequencer/SequencingService.java (working copy)
@@ -65,8 +65,8 @@ import org.modeshape.repository.service.ServiceAdministrator;
import org.modeshape.repository.util.RepositoryNodePath;
/**
- * A sequencing system is used to monitor changes in the content of ModeShape repositories and to sequence the content to extract or to
- * generate structured information.
+ * A sequencing system is used to monitor changes in the content of ModeShape repositories and to sequence the content to extract
+ * or to generate structured information.
*/
public class SequencingService implements AdministeredService {
@@ -227,8 +227,8 @@ public class SequencingService implements AdministeredService {
/**
* Get configurations for all known sequencers
- * @return List of {@link SequencerConfig}s
*
+ * @return List of {@link SequencerConfig}s
* @throws IllegalArgumentException if config
is null
* @see #updateSequencer(SequencerConfig)
* @see #removeSequencer(SequencerConfig)
@@ -236,7 +236,7 @@ public class SequencingService implements AdministeredService {
public List getSequencers() {
return this.sequencerLibrary.getSequenceConfigs();
}
-
+
/**
* Update the configuration for a sequencer, or add it if there is no {@link SequencerConfig#equals(Object) matching
* configuration}.
@@ -435,13 +435,14 @@ public class SequencingService implements AdministeredService {
for (Property property : change.getAddedOrModifiedProperties()) {
Name propertyName = property.getName();
String propertyNameStr = context.getValueFactories().getStringFactory().create(propertyName);
- String path = nodePathStr + "/@" + propertyNameStr;
+ String path = repositorySourceName + ":" + repositoryWorkspaceName + ":" + nodePathStr + "/@"
+ + propertyNameStr;
SequencerPathExpression.Matcher matcher = pathExpression.matcher(path);
if (matcher.matches()) {
// String selectedPath = matcher.getSelectedPath();
RepositoryNodePath outputPath = RepositoryNodePath.parse(matcher.getOutputPath(),
- repositorySourceName,
- repositoryWorkspaceName);
+ matcher.getOutputRepositoryName(),
+ matcher.getOutputWorkspaceName());
SequencerCall call = new SequencerCall(sequencer, propertyNameStr);
// Record the output path ...
Set outputPaths = sequencerCalls.get(call);
@@ -506,20 +507,20 @@ public class SequencingService implements AdministeredService {
}
/**
- * @param sequencersList Sets sequencersList to the specified value.
- */
- public void setSequencersList(List sequencersList) {
- this.sequencersList = sequencersList;
- }
-
- /**
- * @return sequencersList
- */
- public List getSequencersList() {
- return sequencersList;
- }
-
- /**
+ * @param sequencersList Sets sequencersList to the specified value.
+ */
+ public void setSequencersList( List sequencersList ) {
+ this.sequencersList = sequencersList;
+ }
+
+ /**
+ * @return sequencersList
+ */
+ public List getSequencersList() {
+ return sequencersList;
+ }
+
+ /**
* The statistics for the system. Each sequencing system has an instance of this class that is updated.
*
* @author Randall Hauch
Index: modeshape-repository/src/test/java/org/modeshape/repository/sequencer/SequencerPathExpressionTest.java
===================================================================
--- modeshape-repository/src/test/java/org/modeshape/repository/sequencer/SequencerPathExpressionTest.java (revision 2462)
+++ modeshape-repository/src/test/java/org/modeshape/repository/sequencer/SequencerPathExpressionTest.java (working copy)
@@ -27,11 +27,9 @@ 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 org.modeshape.graph.property.PathExpression;
-import org.modeshape.repository.sequencer.InvalidSequencerPathExpression;
-import org.modeshape.repository.sequencer.SequencerPathExpression;
import org.junit.Before;
import org.junit.Test;
+import org.modeshape.graph.property.PathExpression;
/**
* @author Randall Hauch
@@ -115,9 +113,19 @@ public class SequencerPathExpressionTest {
protected void assertMatches( SequencerPathExpression.Matcher matcher,
String selectedPath,
String outputPath ) {
+ assertMatches(matcher, selectedPath, null, null, outputPath);
+ }
+
+ protected void assertMatches( SequencerPathExpression.Matcher matcher,
+ String selectedPath,
+ String outputRepository,
+ String outputWorkspace,
+ String outputPath ) {
assertThat(matcher, is(notNullValue()));
assertThat(matcher.getSelectedPath(), is(selectedPath));
assertThat(matcher.getOutputPath(), is(outputPath));
+ assertThat(matcher.getOutputRepositoryName(), is(outputRepository));
+ assertThat(matcher.getOutputWorkspaceName(), is(outputWorkspace));
if (selectedPath == null) {
assertThat(matcher.matches(), is(false));
} else {
@@ -228,14 +236,20 @@ public class SequencerPathExpressionTest {
@Test
public void shouldMatchExpressionsWithRepositoryInSelectionPath() {
- expr = SequencerPathExpression.compile("reposA:/a/b/c[d/e/@something] => /x/y");
- assertMatches(expr.matcher("reposA:/a/b/c/d/e/@something"), "reposA:/a/b/c", "/x/y");
+ expr = SequencerPathExpression.compile("reposA::/a/b/c[d/e/@something] => /x/y");
+ assertMatches(expr.matcher("reposA::/a/b/c/d/e/@something"), "/a/b/c", "reposA", null, "/x/y");
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithRepositoryAndWorkspaceInSelectionPath() {
+ expr = SequencerPathExpression.compile("reposA::/a/b/c[d/e/@something] => /x/y");
+ assertMatches(expr.matcher("reposA:wsA:/a/b/c/d/e/@something"), "/a/b/c", "reposA", "wsA", "/x/y");
}
@Test
public void shouldMatchExpressionsWithRepositoryInFullOutputPath() {
- expr = SequencerPathExpression.compile("/a/b/c[d/e/@something] => reposA:/x/y");
- assertMatches(expr.matcher("/a/b/c/d/e/@something"), "/a/b/c", "reposA:/x/y");
+ expr = SequencerPathExpression.compile("/a/b/c[d/e/@something] => reposA::/x/y");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"), "/a/b/c", "reposA", null, "/x/y");
}
@Test