From 40da87dca74831fd3eaa9b997644590d0b0c9f6f Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Thu, 27 Jul 2017 14:34:08 +0900 Subject: [PATCH 1/4] JCRVLT-196 Add remapping support for other than renames --- .../vault/fs/api/RegexpPathMapping.java | 112 ++++++++ .../jackrabbit/vault/fs/io/Importer.java | 41 +-- .../jackrabbit/vault/fs/io/MappedArchive.java | 246 ++++++++++++++++++ .../vault/fs/api/PathMappingTest.java | 17 ++ .../integration/TestMappedImport.java | 27 +- .../integration/TestPackageInstall.java | 4 +- 6 files changed, 406 insertions(+), 41 deletions(-) create mode 100644 vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java create mode 100644 vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/MappedArchive.java diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java new file mode 100644 index 000000000..49e22ba78 --- /dev/null +++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java @@ -0,0 +1,112 @@ +/* + * 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.fs.api; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.jackrabbit.vault.fs.api.PathMapping; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Implements a path mapping that supports regular expressions, i.e. {@code /etc/(.*)=/dummy/$1/custom} + * @since 3.1.42 + */ +public final class RegexpPathMapping implements PathMapping { + + private final Map pathsMapping = new HashMap(); + + /** + * Allows importing mappings specified in data structure such as Map or Properties. + * + * All null entries (both keys and values) are ignored. + * + * @param pathsMappingMap the data structure containing the mapping + * @return this + */ + public RegexpPathMapping addAllMappings(@Nonnull Map pathsMappingMap) { + for (Entry entry : pathsMappingMap.entrySet()) { + final K key = entry.getKey(); + final V value = entry.getValue(); + if (key != null && value != null) { + addMapping(String.valueOf(key), String.valueOf(value)); + } + } + return this; + } + + /** + * Add a new mapping based on regular expression. + * + * @param fromPattern the matching pattern, i.e. /etc/(.*) + * @param toPattern the replacing pattern, i.e. /dummy/$1/custom + * @return this + */ + @Nonnull + public RegexpPathMapping addMapping(@Nonnull String fromPattern, @Nonnull String toPattern) { + pathsMapping.put(Pattern.compile(fromPattern), toPattern); + return this; + } + + /** + * Merges the regexp mapping from the given base mapping. + * + * @param base base mapping + * @return this + */ + @Nonnull + public RegexpPathMapping merge(@Nullable RegexpPathMapping base) { + if (base != null) { + this.pathsMapping.putAll(base.pathsMapping); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + @Nonnull + public String map(@Nonnull String path) { + return map(path, false); + } + + /** + * {@inheritDoc} + */ + @Override + @Nonnull + public String map(@Nonnull String path, boolean reverse) { + if (reverse) { + throw new IllegalArgumentException("path mapping cannot be reversed with the regexp mapping"); + } else { + for (Entry pathMapping : pathsMapping.entrySet()) { + Matcher matcher = pathMapping.getKey().matcher(path); + if (matcher.matches()) { + return matcher.replaceAll(pathMapping.getValue()); + } + } + return path; + } + } + +} diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java index 9b99d2cb9..f7adefad9 100644 --- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java +++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java @@ -226,11 +226,6 @@ public class Importer { */ private final ImportOptions opts; - /** - * path mapping from the import options - */ - private PathMapping pathMapping; - /** * the checkpoint state of the autosave. used for recovering from stale item errors during install. */ @@ -366,11 +361,11 @@ public void run(Archive archive, Node importRoot) } // check path remapping - pathMapping = opts.getPathMapping(); + PathMapping pathMapping = opts.getPathMapping(); if (pathMapping != null) { filter = filter.translate(pathMapping); - } else { - pathMapping = PathMapping.IDENTITY; + this.archive = archive = new MappedArchive(archive, pathMapping); + this.archive.open(true); } // set import mode if possible @@ -594,9 +589,7 @@ private TxInfo prepare(Archive.Entry jcrRoot, String parentPath, NamespaceResolv private void prepare(Archive.Entry directory, TxInfo parentInfo, NamespaceResolver resolver) throws IOException, RepositoryException { Collection files = directory.getChildren(); - if (files == null) { - return; - } + // first process the directories for (Archive.Entry file: files) { if (file.isDirectory()) { @@ -612,19 +605,6 @@ private void prepare(Archive.Entry directory, TxInfo parentInfo, NamespaceResolv repoPath = parentInfo.path + "/" + repoName; } - // remap if needed - String mappedPath = pathMapping.map(repoPath); - if (!mappedPath.equals(repoPath)) { - String mappedParent = Text.getRelativeParent(mappedPath, 1); - if (!mappedParent.equals(parentInfo.path)) { - log.warn("remapping other than renames not supported yet ({} -> {}).", repoPath, mappedPath); - } else { - log.debug("remapping detected {} -> {}", repoPath, mappedPath); - repoPath = mappedPath; - repoName = Text.getName(repoPath); - } - } - TxInfo info = parentInfo.addChild(new TxInfo(parentInfo, repoPath)); log.trace("Creating directory artifact for {}", repoName); Artifact parent = new DirectoryArtifact(repoName); @@ -676,19 +656,6 @@ private void prepare(Archive.Entry directory, TxInfo parentInfo, NamespaceResolv subPackages.add(repoPath); } - // remap if needed - String mappedPath = pathMapping.map(repoPath); - if (!mappedPath.equals(repoPath)) { - String mappedParent = Text.getRelativeParent(mappedPath, 1); - if (!mappedParent.equals(parentInfo.path)) { - log.warn("remapping other than renames not supported yet ({} -> {}).", repoPath, mappedPath); - } else { - log.debug("remapping detected {} -> {}", repoPath, mappedPath); - repoPath = mappedPath; - repoName = Text.getName(repoPath); - } - } - String repoBase = repoName; String ext = ""; int idx = repoName.lastIndexOf('.'); diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/MappedArchive.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/MappedArchive.java new file mode 100644 index 000000000..a1ae6d341 --- /dev/null +++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/MappedArchive.java @@ -0,0 +1,246 @@ +/* + * 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.fs.io; + +import org.apache.jackrabbit.vault.fs.api.PathMapping; +import org.apache.jackrabbit.vault.fs.api.VaultInputSource; +import org.apache.jackrabbit.vault.fs.config.MetaInf; +import org.apache.jackrabbit.vault.util.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Implements an archive that remaps the entries of an underlying archive using a {@link PathMapping}. + */ +public class MappedArchive extends AbstractArchive { + + /** + * default logger + */ + private static final Logger log = LoggerFactory.getLogger(MappedArchive.class); + + private final Archive base; + + private final PathMapping mapping; + + private final VirtualEntry root = new VirtualEntry(); + + private VirtualEntry jcrRoot; + + public MappedArchive(Archive base, PathMapping mapping) { + this.base = base; + this.mapping = mapping; + } + + @Override + public void open(boolean strict) throws IOException { + base.open(strict); + applyMapping(base.getRoot(), root); + } + + /** + * Decorates the non jcr files without applying the mapping. this can be done with parallel traversal. + * @param src source entry parent + * @param dst destination entry parent + */ + private void applyMapping(@Nonnull Entry src, @Nonnull VirtualEntry dst) { + for (Entry child: src.getChildren()) { + VirtualEntry dstChild = dst.add(child.getName(), child); + if ("/jcr_root".equals(dstChild.getPath())) { + jcrRoot = dstChild; + applyMapping(child, ""); + } else { + applyMapping(child, dstChild); + } + } + } + + /** + * Decorates the jcr entries while applying the path mapping. this cannot be done with parallel traversal, because + * the remapping could create holes in the entry tree. + * + * @param src the source entry + * @param jcrPath the jcr path of the source entry + */ + private void applyMapping(@Nonnull Entry src, @Nonnull String jcrPath) { + for (Entry child: src.getChildren()) { + String path = jcrPath + "/" + child.getName(); + String mappedPath = mapping.map(path); + + // add entry to tree + String[] segments = Text.explode(mappedPath, '/'); + VirtualEntry entry = jcrRoot; + for (String seg: segments) { + entry = entry.add(seg, null); + } + if (entry.baseEntry != null) { + log.warn("Path mapping maps multiple paths to the same destination: {} -> {}. ignoring this source.", path, mappedPath); + } else { + entry.baseEntry = child; + } + + applyMapping(child, path); + } + } + + @Override + @CheckForNull + public InputStream openInputStream(@Nullable Entry entry) throws IOException { + if (entry == null) { + return null; + } + return base.openInputStream(((VirtualEntry) entry).baseEntry); + } + + @Override + @CheckForNull + public VaultInputSource getInputSource(@Nullable Entry entry) throws IOException { + if (entry == null) { + return null; + } + return base.getInputSource(((VirtualEntry) entry).baseEntry); + } + + @Override + @Nonnull + public Entry getRoot() throws IOException { + return root; + } + + @Override + public Entry getJcrRoot() throws IOException { + return jcrRoot; + } + + @Override + @Nonnull + public MetaInf getMetaInf() { + return base.getMetaInf(); + } + + @Override + public void close() { + base.close(); + } + + /** + * Implements a entry for this archive + */ + private static class VirtualEntry implements Entry { + + @Nullable + private final VirtualEntry parent; + + @Nonnull + private final String name; + + @Nullable + private Archive.Entry baseEntry; + + @Nullable + private Map children; + + private VirtualEntry() { + this.parent = null; + this.name = ""; + this.baseEntry = null; + } + + private VirtualEntry(@Nonnull VirtualEntry parent, @Nonnull String name, @Nullable Archive.Entry baseEntry) { + this.parent = parent; + this.name = name; + this.baseEntry = baseEntry; + } + + /** + * {@inheritDoc} + */ + @Override + @Nonnull + public String getName() { + return name; + } + + @Nonnull + public String getPath() { + return getPath(new StringBuilder()).toString(); + } + + @Nonnull + private StringBuilder getPath(@Nonnull StringBuilder sb) { + return parent == null ? sb : parent.getPath(sb).append('/').append(name); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDirectory() { + return baseEntry == null || baseEntry.isDirectory(); + } + + /** + * {@inheritDoc} + */ + @Override + @Nonnull + public Collection getChildren() { + return children == null ? Collections.emptyList() : children.values(); + } + + /** + * {@inheritDoc} + */ + @Override + @Nullable + public Entry getChild(String name) { + return children == null ? null : children.get(name); + } + + /** + * Adds a new child entry. + * @param name name + * @param baseEntry the base archive's entry or + * @return the new entry + */ + @Nonnull + public VirtualEntry add(@Nonnull String name, @Nullable Archive.Entry baseEntry) { + if (children != null) { + VirtualEntry ret = children.get(name); + if (ret != null) { + return ret; + } + } + VirtualEntry ve = new VirtualEntry(this, name, baseEntry); + if (children == null) { + children = new LinkedHashMap(); + } + children.put(name, ve); + return ve; + } + } +} \ No newline at end of file diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java index 169f18542..63d9b4b5a 100644 --- a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java +++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java @@ -16,6 +16,8 @@ */ package org.apache.jackrabbit.vault.fs.api; +import static org.junit.Assert.assertEquals; + import org.junit.Test; import static junit.framework.Assert.assertEquals; @@ -77,4 +79,19 @@ public void testMultiReverse() { assertEquals("/dest/foo/1/top/flop", map.map("/test/flop", true)); } + + @Test + public void testRegexpIdentityMapping() { + RegexpPathMapping pathMapping = new RegexpPathMapping(); + assertEquals("/etc/my/fake/data", pathMapping.map("/etc/my/fake/data")); + } + + @Test + public void testRegexpCorrectMapping() { + RegexpPathMapping pathMapping = new RegexpPathMapping(); + pathMapping.addMapping("/etc/(.*)", "/dummy/$1/custom"); + + assertEquals("/dummy/my/fake/data/custom", pathMapping.map("/etc/my/fake/data")); + } + } \ No newline at end of file diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestMappedImport.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestMappedImport.java index 2a83d5c2e..8382b82a7 100644 --- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestMappedImport.java +++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestMappedImport.java @@ -22,6 +22,7 @@ import javax.jcr.RepositoryException; import org.apache.jackrabbit.vault.fs.api.MultiPathMapping; +import org.apache.jackrabbit.vault.fs.api.RegexpPathMapping; import org.apache.jackrabbit.vault.fs.io.ImportOptions; import org.apache.jackrabbit.vault.packaging.JcrPackage; import org.apache.jackrabbit.vault.packaging.PackageException; @@ -29,6 +30,8 @@ import org.junit.Ignore; import org.junit.Test; +import static org.junit.Assert.assertNotNull; + /** */ public class TestMappedImport extends IntegrationTestBase { @@ -167,7 +170,6 @@ public void testNested() throws RepositoryException, IOException, PackageExcepti * Tests if a non-trivial rename remapping works. * This is currently not supported */ - @Ignore("JCRVLT-78") @Test public void testNonRename() throws RepositoryException, IOException, PackageException { JcrPackage pack = packMgr.upload(getStream("testpackages/tmp_foo.zip"), false); @@ -178,7 +180,28 @@ public void testNonRename() throws RepositoryException, IOException, PackageExce opts.setPathMapping(mapping); pack.extract(opts); - assertNodeExists("/tmp/foo"); + assertNodeMissing("/tmp/foo"); assertNodeExists("/libs/foo"); } + + /** + * Tests if regexp remap works + */ + @Test + public void testInstallWithRegexp() throws RepositoryException, IOException, PackageException { + JcrPackage pack = packMgr.upload(getStream("testpackages/test_version.zip"), false); + assertNotNull(pack); + + ImportOptions opts = getDefaultOptions(); + RegexpPathMapping pathMapping = new RegexpPathMapping(); + pathMapping.addMapping("/testroot/(.*)", "/root/$1"); + opts.setPathMapping(pathMapping); + + pack.install(opts); + + assertNodeExists("/root/a"); + assertNodeMissing("/testroot/a"); + } + + } \ No newline at end of file diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestPackageInstall.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestPackageInstall.java index cbb822065..a4f5a3626 100644 --- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestPackageInstall.java +++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestPackageInstall.java @@ -82,7 +82,7 @@ public void testRewrap() throws RepositoryException, IOException, PackageExcepti */ @Test public void testUnwrapPreserveInstall() throws RepositoryException, IOException, PackageException { - + JcrPackage pack = packMgr.upload(getStream("testpackages/tmp.zip"), true, true); assertNotNull(pack); assertTrue(pack.isValid()); @@ -106,7 +106,7 @@ public void testUnwrapPreserveInstall() throws RepositoryException, IOException, assertTrue(pack.isValid()); assertTrue(pack.isInstalled()); assertEquals(lastUnpacked, pack.getDefinition().getLastUnpacked().getTimeInMillis()); - + // a package with a different created date should not preserve the status! pack = packMgr.upload(getStream("testpackages/tmp_with_modified_created_date.zip"), true, true); assertNotNull(pack); From a90f7315848e245dc32a89b13ef75e3760ad8861 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Fri, 28 Jul 2017 10:45:44 +0900 Subject: [PATCH 2/4] JCRVLT-196 Add remapping support for other than renames --- .../vault/fs/api/RegexpPathMapping.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java index 49e22ba78..4504e9764 100644 --- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java +++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/RegexpPathMapping.java @@ -43,6 +43,7 @@ public final class RegexpPathMapping implements PathMapping { * @param pathsMappingMap the data structure containing the mapping * @return this */ + @Nonnull public RegexpPathMapping addAllMappings(@Nonnull Map pathsMappingMap) { for (Entry entry : pathsMappingMap.entrySet()) { final K key = entry.getKey(); @@ -87,7 +88,13 @@ public RegexpPathMapping merge(@Nullable RegexpPathMapping base) { @Override @Nonnull public String map(@Nonnull String path) { - return map(path, false); + for (Entry pathMapping : pathsMapping.entrySet()) { + Matcher matcher = pathMapping.getKey().matcher(path); + if (matcher.matches()) { + return matcher.replaceAll(pathMapping.getValue()); + } + } + return path; } /** @@ -97,16 +104,9 @@ public String map(@Nonnull String path) { @Nonnull public String map(@Nonnull String path, boolean reverse) { if (reverse) { - throw new IllegalArgumentException("path mapping cannot be reversed with the regexp mapping"); - } else { - for (Entry pathMapping : pathsMapping.entrySet()) { - Matcher matcher = pathMapping.getKey().matcher(path); - if (matcher.matches()) { - return matcher.replaceAll(pathMapping.getValue()); - } - } - return path; + throw new IllegalArgumentException("No reverse mapping not supported with regexp mapping"); } + return map(path); } } From 0ca93700631054d1b9426516407594416194ff74 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Fri, 28 Jul 2017 10:51:07 +0900 Subject: [PATCH 3/4] JCRVLT-196 Add remapping support for other than renames --- .../org/apache/jackrabbit/vault/fs/api/PathMappingTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java index 63d9b4b5a..28b543f87 100644 --- a/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java +++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/api/PathMappingTest.java @@ -16,8 +16,6 @@ */ package org.apache.jackrabbit.vault.fs.api; -import static org.junit.Assert.assertEquals; - import org.junit.Test; import static junit.framework.Assert.assertEquals; From 3dd282682a98d5100e84867c3e355129f33b6de0 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Fri, 28 Jul 2017 10:59:21 +0900 Subject: [PATCH 4/4] JCRVLT-196 Add remapping support for other than renames --- .../java/org/apache/jackrabbit/vault/fs/api/package-info.java | 2 +- .../java/org/apache/jackrabbit/vault/fs/io/package-info.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java index 0201a72c4..55a8616f2 100644 --- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java +++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java @@ -15,7 +15,7 @@ * limitations under the License. */ -@Version("2.4.0") +@Version("2.5.0") package org.apache.jackrabbit.vault.fs.api; import org.osgi.annotation.versioning.Version; \ No newline at end of file diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java index 846a8332e..f55322297 100644 --- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java +++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java @@ -15,7 +15,7 @@ * limitations under the License. */ -@Version("2.5.0") +@Version("2.6.0") package org.apache.jackrabbit.vault.fs.io; import org.osgi.annotation.versioning.Version; \ No newline at end of file