diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java index a1c27cd..1c5f362 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/WorkspaceFilter.java @@ -25,25 +25,31 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; /** - * WorkspaceFilter... + * WorkspaceFilter defined a filter for items (node or property). */ public interface WorkspaceFilter extends Dumpable { /** - * Returns a list of path filter sets. + * Returns a list of path filter sets for node items. * @return the list of path filter sets. */ List getFilterSets(); /** - * Returns the filter set that covers the respective path + * Returns a list of path filter sets for property items. + * @return the list of path filter sets. + */ + List getPropertyFilterSets(); + + /** + * Returns the filter set that covers the respective node path * @param path the path * @return the filter set or null */ PathFilterSet getCoveringFilterSet(String path); /** - * Returns the import mode for the given path. + * Returns the import mode for the given node path. * @param path path to check * @return the import mode or {@link ImportMode#REPLACE} if the given path * is not covered by this filter. @@ -51,7 +57,7 @@ public interface WorkspaceFilter extends Dumpable { ImportMode getImportMode(String path); /** - * Checks if the given path is contained in this workspace filter. + * Checks if the given node path is contained in this workspace filter. * It returns true if any of the filter sets contain the path * and it's not globally ignored. * @@ -61,7 +67,7 @@ public interface WorkspaceFilter extends Dumpable { boolean contains(String path); /** - * Checks if the given path is covered in this workspace filter. + * Checks if the given node path is covered in this workspace filter. * It only returns true if at least one of the sets covers * the path and is not globally ignored. * @@ -71,7 +77,7 @@ public interface WorkspaceFilter extends Dumpable { boolean covers(String path); /** - * Checks if the given path is an ancestor of any of the filter sets. + * Checks if the given node path is an ancestor of any of the filter sets. * * @param path the item to check * @return true if the given item is an ancestor @@ -79,7 +85,7 @@ public interface WorkspaceFilter extends Dumpable { boolean isAncestor(String path); /** - * Checks if the given path is globally ignored. + * Checks if the given node path is globally ignored. * * @param path the path to check. * @return true if the item is globally ignored. diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java index 46f036f..9175d81 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/config/DefaultWorkspaceFilter.java @@ -73,7 +73,9 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { */ private static final Logger log = LoggerFactory.getLogger(DefaultWorkspaceFilter.class); - private final List filterSets = new LinkedList(); + private final List nodesFilterSets = new LinkedList(); + + private final List propsFilterSets = new LinkedList(); public static final String ATTR_VERSION = "version"; @@ -93,15 +95,34 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { */ private ImportMode importMode; + /** + * Add a #PathFilterSet for nodes items. + * @param set the set of filters to add. + */ public void add(PathFilterSet set) { - filterSets.add(set); + nodesFilterSets.add(set); + } + + /** + * Add a #PathFilterSet for properties items. + * @param set the set of filters to add. + */ + public void addPropertyFilterSet(PathFilterSet set) { + propsFilterSets.add(set); } /** * {@inheritDoc} */ public List getFilterSets() { - return filterSets; + return nodesFilterSets; + } + + /** + * {@inheritDoc} + */ + public List getPropertyFilterSets() { + return propsFilterSets; } /** @@ -111,7 +132,7 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { if (isGloballyIgnored(path)) { return null; } - for (PathFilterSet set: filterSets) { + for (PathFilterSet set: nodesFilterSets) { if (set.covers(path)) { return set; } @@ -141,7 +162,7 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { if (isGloballyIgnored(path)) { return false; } - for (PathFilterSet set: filterSets) { + for (PathFilterSet set: nodesFilterSets) { if (set.contains(path)) { return true; } @@ -156,7 +177,7 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { if (isGloballyIgnored(path)) { return false; } - for (PathFilterSet set: filterSets) { + for (PathFilterSet set: nodesFilterSets) { if (set.covers(path)) { return true; } @@ -168,7 +189,7 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { * {@inheritDoc} */ public boolean isAncestor(String path) { - for (PathFilterSet set: filterSets) { + for (PathFilterSet set: nodesFilterSets) { if (set.isAncestor(path)) { return true; } @@ -194,9 +215,12 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { if (globalIgnored != null) { mapped.setGlobalIgnored(globalIgnored.translate(mapping)); } - for (PathFilterSet set: filterSets) { + for (PathFilterSet set: nodesFilterSets) { mapped.add(set.translate(mapping)); } + for (PathFilterSet set: propsFilterSets) { + mapped.addPropertyFilterSet(set.translate(mapping)); + } return mapped; } @@ -284,35 +308,41 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { if (!"filter".equals(child.getNodeName())) { throw new ConfigurationException(" expected."); } - PathFilterSet def = readDef((Element) child); - filterSets.add(def); + readDef((Element) child); } } } - private PathFilterSet readDef(Element elem) throws ConfigurationException { + private void readDef(Element elem) throws ConfigurationException { String root = elem.getAttribute("root"); - PathFilterSet def = new PathFilterSet(root == null || root.length() == 0 ? "/" : root); + PathFilterSet nodeFilters = new PathFilterSet(root == null || root.length() == 0 ? "/" : root); + PathFilterSet propFilters = new PathFilterSet(); // check for import mode String mode = elem.getAttribute("mode"); if (mode != null && mode.length() > 0) { - def.setImportMode(ImportMode.valueOf(mode.toUpperCase())); + ImportMode importMode = ImportMode.valueOf(mode.toUpperCase()); + nodeFilters.setImportMode(importMode); + propFilters.setImportMode(importMode); } // check for filters NodeList n1 = elem.getChildNodes(); for (int i=0; i or expected."); } } } - return def; + add(nodeFilters); + addPropertyFilterSet(propFilters); } protected PathFilter readFilter(Element elem) throws ConfigurationException { @@ -327,7 +357,7 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { * {@inheritDoc} */ public void dump(DumpContext ctx, boolean isLast) { - Iterator iter = filterSets.iterator(); + Iterator iter = nodesFilterSets.iterator(); while (iter.hasNext()) { PathFilterSet set = iter.next(); ctx.println(!iter.hasNext(), "ItemFilterSet"); @@ -354,7 +384,7 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute(null, null, ATTR_VERSION, "CDATA", String.valueOf(version)); ser.startElement(null, null, "workspaceFilter", attrs); - for (PathFilterSet set: filterSets) { + for (PathFilterSet set: nodesFilterSets) { attrs = new AttributesImpl(); attrs.addAttribute(null, null, "root", "CDATA", set.getRoot()); if (set.getImportMode() != ImportMode.REPLACE) { @@ -411,7 +441,7 @@ public class DefaultWorkspaceFilter implements Dumpable, WorkspaceFilter { ProgressTracker tracker = new ProgressTracker(listener); // get common ancestor Tree tree = new Tree(); - for (PathFilterSet set: filterSets) { + for (PathFilterSet set: nodesFilterSets) { tree.put(set.getRoot(), set); } String rootPath = tree.getRootPath(); diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateImpl.java vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateImpl.java index d70da46..025628c 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateImpl.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateImpl.java @@ -572,6 +572,15 @@ public class AggregateImpl implements Aggregate { } } + private boolean includeProperty(String propertyPath) { + for (PathFilterSet filterSet : mgr.getWorkspaceFilter().getPropertyFilterSets()) { + if (! filterSet.contains(propertyPath)) { + return false; + } + } + return true; + } + private void addNamespace(Set prefixes, String name) throws RepositoryException { int idx = name.indexOf(':'); if (idx > 0) { @@ -676,7 +685,7 @@ public class AggregateImpl implements Aggregate { while (pIter.hasNext()) { Property p = pIter.nextProperty(); String path = p.getPath(); - if (aggregator.includes(getNode(), node, p, path)) { + if (aggregator.includes(getNode(), node, p, path) && includeProperty(path)) { include(node, p, path); } } diff --git vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java index 56e2def..06a0584 100644 --- vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java +++ vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/IntegrationTestBase.java @@ -273,6 +273,11 @@ public class IntegrationTestBase { assertEquals(path + " should contain " + value, value, admin.getProperty(path).getString()); } + public void assertPropertyExists(String path) throws RepositoryException { + assertTrue(path + " should exist", admin.propertyExists(path)); + } + + public void assertProperty(String path, String[] values) throws RepositoryException { ArrayList strings = new ArrayList(); for (Value v: admin.getProperty(path).getValues()) { diff --git vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFilteredPropertyExport.java vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFilteredPropertyExport.java new file mode 100644 index 0000000..54d1d3f --- /dev/null +++ vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestFilteredPropertyExport.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jackrabbit.vault.packaging.integration; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.vault.fs.api.PathFilterSet; +import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter; +import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf; +import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; +import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter; +import org.apache.jackrabbit.vault.packaging.ExportOptions; +import org.apache.jackrabbit.vault.packaging.PackageException; +import org.apache.jackrabbit.vault.packaging.VaultPackage; +import org.junit.Before; +import org.junit.Test; + +/** + * TestFilteredExport cover testing the filtering of properties + */ +public class TestFilteredPropertyExport extends IntegrationTestBase { + + @Before + public void setUp() throws Exception { + super.setUp(); + setupTmpFooBarWithProperties(admin); + admin.save(); + } + + @Test + public void noPropertyFiltered() throws IOException, RepositoryException, PackageException { + DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); + filter.add(new PathFilterSet("/tmp")); + // export and extract + File pkgFile = assemblePackage(filter); + clean("/tmp"); + packMgr.open(pkgFile).extract(admin, getDefaultOptions()); + // validate the extracted content + assertPropertiesExist("/tmp", "p1", "p2", "p3"); + assertPropertiesExist("/tmp/foo", "p1", "p2", "p3"); + assertPropertiesExist("/tmp/foo/bar", "p1", "p2", "p3"); + } + + @Test + public void filterPropertyP1OnFoo() throws IOException, RepositoryException, PackageException { + DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); + filter.add(new PathFilterSet("/tmp")); + + PathFilterSet properties = new PathFilterSet("/tmp"); + properties.addExclude(new DefaultPathFilter("/tmp/foo/p1")); + filter.addPropertyFilterSet(properties); + // export and extract + File pkgFile = assemblePackage(filter); + clean("/tmp"); + packMgr.open(pkgFile).extract(admin, getDefaultOptions()); + // validate the extracted content + assertPropertiesExist("/tmp", "p1", "p2", "p3"); + assertPropertiesExist("/tmp/foo", "p2", "p3"); + assertPropertiesMissg("/tmp/foo", "p1" ); + assertPropertiesExist("/tmp/foo/bar", "p1", "p2", "p3"); + } + + @Test + public void filterPropertyPxOnFoo() throws IOException, RepositoryException, PackageException { + DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); + filter.add(new PathFilterSet("/tmp")); + + PathFilterSet properties = new PathFilterSet("/tmp"); + properties.addExclude(new DefaultPathFilter("/tmp/foo/p.*")); + filter.addPropertyFilterSet(properties); + // export and extract + File pkgFile = assemblePackage(filter); + clean("/tmp"); + packMgr.open(pkgFile).extract(admin, getDefaultOptions()); + // validate the extracted content + assertPropertiesExist("/tmp", "p1", "p2", "p3"); + assertPropertiesMissg("/tmp/foo", "p1", "p2", "p3"); + assertPropertiesExist("/tmp/foo/bar", "p1", "p2", "p3"); + } + + @Test + public void filterRelativeProperties() throws IOException, RepositoryException, PackageException { + DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); + filter.add(new PathFilterSet("/tmp")); + + PathFilterSet properties = new PathFilterSet("/tmp"); + properties.addExclude(new DefaultPathFilter(".*/p1")); + filter.addPropertyFilterSet(properties); + // export and extract + File pkgFile = assemblePackage(filter); + clean("/tmp"); + packMgr.open(pkgFile).extract(admin, getDefaultOptions()); + // validate the extracted content + assertPropertiesExist("/tmp", "p2", "p3"); + assertPropertiesMissg("/tmp", "p1" ); + assertPropertiesExist("/tmp/foo", "p2", "p3"); + assertPropertiesMissg("/tmp/foo", "p1" ); + assertPropertiesExist("/tmp/foo/bar", "p2", "p3"); + assertPropertiesMissg("/tmp/foo/bar", "p1" ); + } + + + /** + * Setup the path /tmp/foo/bar with properties set at each level + */ + private void setupTmpFooBarWithProperties(Session session) + throws RepositoryException { + Node root = session.getRootNode(); + Node tmp = setupProperties(root.addNode("tmp")); + Node foo = setupProperties(tmp.addNode("foo")); + Node bar = setupProperties(foo.addNode("bar")); + } + + private Node setupProperties(Node node) + throws RepositoryException { + node.setProperty("p1", "v1"); + node.setProperty("p2", "v2"); + node.setProperty("p3", "v3"); + return node; + } + + private File assemblePackage(WorkspaceFilter filter) + throws IOException, RepositoryException { + + File tmpFile = File.createTempFile("vaulttest", "zip"); + + ExportOptions options = new ExportOptions(); + DefaultMetaInf meta = new DefaultMetaInf(); + meta.setFilter(filter); + + Properties props = new Properties(); + props.setProperty(VaultPackage.NAME_GROUP, "jackrabbit/test"); + props.setProperty(VaultPackage.NAME_NAME, "filtered-export-package"); + meta.setProperties(props); + + options.setMetaInf(meta); + + packMgr.assemble(admin, options, tmpFile).close(); + return tmpFile; + } + + private void assertPropertiesExist(String rootPath, String ... propNames) + throws RepositoryException { + for (String propName : propNames) { + String propPath = String.format("%s/%s", rootPath, propName); + assertPropertyExists(propPath); + } + } + + private void assertPropertiesMissg(String rootPath, String ... propNames) + throws RepositoryException { + for (String propName : propNames) { + String propPath = String.format("%s/%s", rootPath, propName); + assertPropertyMissing(propPath); + } + } + +} \ No newline at end of file