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: *

* * - * + * * * * @@ -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 PathPath expressionDescription
* - * + * * * * @@ -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
Input PathPath expressionsDescription