diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/fs/Mounter.java vault-core/src/main/java/org/apache/jackrabbit/vault/fs/Mounter.java index 37a13e7..7ff8ee5 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/fs/Mounter.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/Mounter.java @@ -60,7 +60,36 @@ public final class Mounter { throws RepositoryException, IOException { return new VaultFileSystemImpl( AggregateManagerImpl.mount( - config, wspFilter, mountpoint, session + config, wspFilter, mountpoint, session, false + ).getRoot(), + rootPath, + true + ); + } + + + /** + * Mounts a new Vault filesystem on the given repository node. + * + * @param config vault fs config + * @param wspFilter the workspace filter + * @param mountpoint the address of the mountpoint + * @param rootPath path of root file. used for remapping + * @param session the repository session + * @param useBinaryReferences whether to serialize the binaries as references + * @return a Vault filesystem + * @throws RepositoryException if an error occurs. + * @throws IOException if an I/O error occurs. + */ + public static VaultFileSystem mount(VaultFsConfig config, + WorkspaceFilter wspFilter, + RepositoryAddress mountpoint, + String rootPath, + Session session, boolean useBinaryReferences) + throws RepositoryException, IOException { + return new VaultFileSystemImpl( + AggregateManagerImpl.mount( + config, wspFilter, mountpoint, session, useBinaryReferences ).getRoot(), rootPath, true 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 96afa22..ae2eb16 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 @@ -27,6 +27,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; @@ -35,6 +36,7 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; +import org.apache.jackrabbit.api.ReferenceBinary; import org.apache.jackrabbit.vault.fs.api.Aggregate; import org.apache.jackrabbit.vault.fs.api.Aggregator; import org.apache.jackrabbit.vault.fs.api.Artifact; @@ -542,10 +544,30 @@ public class AggregateImpl implements Aggregate { include(parent, null); includes.add(mgr.cacheString(relPath)); if (prop.getType() == PropertyType.BINARY) { - if (binaries == null) { - binaries = new LinkedList(); + + boolean includeBinary = true; + + if (mgr.useBinaryReferences()) { + Binary bin = prop.getBinary(); + + if (bin != null && bin instanceof ReferenceBinary) { + String binaryReference = ((ReferenceBinary) bin).getReference(); + + // do not create a separate binary file if there is a reference + if (binaryReference != null) { + includeBinary = false; + } + } } - binaries.add(prop); + + if (includeBinary) { + if (binaries == null) { + binaries = new LinkedList(); + } + binaries.add(prop); + } + + } } } diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateManagerImpl.java vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateManagerImpl.java index ec52871..bbc0ac9 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateManagerImpl.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregateManagerImpl.java @@ -135,6 +135,9 @@ public class AggregateManagerImpl implements AggregateManager { */ private final AggregateImpl root; + private final boolean useBinaryReferences; + + /** * Creates a new artifact manager that is rooted at the given node. * @@ -148,7 +151,7 @@ public class AggregateManagerImpl implements AggregateManager { public static AggregateManager mount(VaultFsConfig config, WorkspaceFilter wspFilter, RepositoryAddress mountpoint, - Session session) + Session session, boolean useBinaryReferences) throws RepositoryException { assert mountpoint.getWorkspace().equals(session.getWorkspace().getName()); if (config == null) { @@ -158,7 +161,7 @@ public class AggregateManagerImpl implements AggregateManager { wspFilter = getDefaultWorkspaceFilter(); } Node rootNode = session.getNode(mountpoint.getPath()); - return new AggregateManagerImpl(config, wspFilter, mountpoint, rootNode, false); + return new AggregateManagerImpl(config, wspFilter, mountpoint, rootNode, false, useBinaryReferences); } /** @@ -199,7 +202,7 @@ public class AggregateManagerImpl implements AggregateManager { throw e; } } - return new AggregateManagerImpl(config, wspFilter, mountpoint, rootNode, true); + return new AggregateManagerImpl(config, wspFilter, mountpoint, rootNode, true, false); } /** @@ -272,12 +275,13 @@ public class AggregateManagerImpl implements AggregateManager { */ private AggregateManagerImpl(VaultFsConfig config, WorkspaceFilter wspFilter, RepositoryAddress mountpoint, Node rootNode, - boolean ownSession) + boolean ownSession, boolean useBinaryReferences) throws RepositoryException { session = rootNode.getSession(); this.mountpoint = mountpoint; this.ownSession = ownSession; this.config = config; + this.useBinaryReferences = useBinaryReferences; workspaceFilter = wspFilter; aggregatorProvider = new AggregatorProvider(config.getAggregators()); artifactHandlers = Collections.unmodifiableList(config.getHandlers()); @@ -428,7 +432,22 @@ public class AggregateManagerImpl implements AggregateManager { } public Aggregator getAggregator(Node node, String path) throws RepositoryException { + boolean useDefaultAggregator = false; + if (useBinaryReferences && (node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_RESOURCE))) { + useDefaultAggregator = true; + } + + if (useDefaultAggregator) { + Aggregator defaultAggregator = aggregatorProvider.getDefaultAggregator(); + + if (defaultAggregator != null) { + return defaultAggregator; + } + } + return aggregatorProvider.getAggregator(node, path); + + } public WorkspaceFilter getWorkspaceFilter() { @@ -524,6 +543,10 @@ public class AggregateManagerImpl implements AggregateManager { return config; } + public boolean useBinaryReferences() { + return useBinaryReferences; + } + private static class AggregatorTracker { /** diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java index 3c07648..5da3f98 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/AggregatorProvider.java @@ -37,12 +37,24 @@ public class AggregatorProvider { */ private final List aggregators; + private final Aggregator defaultAggregator; + /** * Constructs a new aggregator provider with a given aggregator list. * @param aggregators the list of aggregators. */ public AggregatorProvider(List aggregators) { this.aggregators = Collections.unmodifiableList(aggregators); + + Aggregator defaultAgg = null; + for (Aggregator a: aggregators) { + if (a.isDefault()) { + defaultAgg = a; + break; + } + } + + this.defaultAggregator = defaultAgg; } /** @@ -71,6 +83,11 @@ public class AggregatorProvider { return null; } + + public Aggregator getDefaultAggregator() { + return defaultAggregator; + } + /** * {@inheritDoc} */ diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java index b30c4fc..642ad03 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java @@ -142,13 +142,14 @@ public class DocViewSAXFormatter extends AbstractSAXFormatter { // attributes (properties) AttributesImpl attrs = new AttributesImpl(); Collections.sort(props, ItemNameComparator.INSTANCE); + boolean useBinaryReferences = ((AggregateManagerImpl) aggregate.getManager()).useBinaryReferences(); for (Property prop: props) { // attribute name (encode property name to make sure it's a valid xml name) String attrName = ISO9075.encode(prop.getName()); Name qName = getQName(attrName); boolean sort = qName.equals(NameConstants.JCR_MIXINTYPES); attrs.addAttribute(qName.getNamespaceURI(), qName.getLocalName(), - attrName, CDATA_TYPE, DocViewProperty.format(prop, sort)); + attrName, CDATA_TYPE, DocViewProperty.format(prop, sort, useBinaryReferences)); } // start element (node) diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageProperties.java vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageProperties.java index 322fc9d..26133f6 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageProperties.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageProperties.java @@ -128,6 +128,12 @@ public interface PackageProperties { */ String NAME_SUB_PACKAGE_HANDLING = "subPackageHandling"; + + /** + * Name of the flag that configures whether to use binary references instead of actualy binary + */ + String NAME_USE_BINARY_REFERENCES = "useBinaryReferences"; + /** * the prefix for an install hook property. eg: * 'installhook.test1.class = ....' diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java index ed639fd..be7c386 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/PackageManagerImpl.java @@ -43,6 +43,7 @@ import org.apache.jackrabbit.vault.fs.io.JarExporter; import org.apache.jackrabbit.vault.fs.spi.ProgressTracker; import org.apache.jackrabbit.vault.packaging.ExportOptions; import org.apache.jackrabbit.vault.packaging.PackageManager; +import org.apache.jackrabbit.vault.packaging.PackageProperties; import org.apache.jackrabbit.vault.packaging.VaultPackage; import org.apache.jackrabbit.vault.util.Constants; @@ -110,7 +111,15 @@ public class PackageManagerImpl implements PackageManager { if (metaInf == null) { metaInf = new DefaultMetaInf(); } - VaultFileSystem jcrfs = Mounter.mount(metaInf.getConfig(), metaInf.getFilter(), addr, opts.getRootPath(), s); + + boolean useBinaryReferences = false; + + if (metaInf.getProperties() != null) { + useBinaryReferences = "true".equals(metaInf.getProperties().getProperty(PackageProperties.NAME_USE_BINARY_REFERENCES)); + } + + + VaultFileSystem jcrfs = Mounter.mount(metaInf.getConfig(), metaInf.getFilter(), addr, opts.getRootPath(), s, useBinaryReferences); JarExporter exporter = new JarExporter(out); exporter.setProperties(metaInf.getProperties()); if (opts.getListener() != null) { diff --git vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty.java vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty.java index 550ef50..e552435 100644 --- vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty.java +++ vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty.java @@ -23,6 +23,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyType; @@ -30,6 +31,8 @@ import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; +import org.apache.jackrabbit.api.ReferenceBinary; +import org.apache.jackrabbit.commons.jackrabbit.SimpleReferenceBinary; import org.apache.jackrabbit.util.XMLChar; import org.apache.jackrabbit.value.ValueHelper; @@ -42,6 +45,8 @@ import org.apache.jackrabbit.value.ValueHelper; */ public class DocViewProperty { + private static final String BINARY_REF = "BinaryRef"; + /** * name of the property */ @@ -64,6 +69,11 @@ public class DocViewProperty { public final int type; /** + * indicates a binary ref property + */ + public final boolean isRef; + + /** * set of unambigous property names */ private static final Set UNAMBIGOUS = new HashSet(); @@ -81,7 +91,12 @@ public class DocViewProperty { * @throws IllegalArgumentException if single value property and not * exactly 1 value is given. */ + public DocViewProperty(String name, String[] values, boolean multi, int type) { + this(name, values, multi, type, false); + } + + public DocViewProperty(String name, String[] values, boolean multi, int type, boolean ref) { this.name = name; this.values = values; isMulti = multi; @@ -95,6 +110,7 @@ public class DocViewProperty { if (!isMulti && values.length != 1) { throw new IllegalArgumentException("Single value property needs exactly 1 value."); } + this.isRef = ref; } /** @@ -105,6 +121,7 @@ public class DocViewProperty { */ public static DocViewProperty parse(String name, String value) { boolean isMulti = false; + boolean isBinaryRef = false; int type = PropertyType.UNDEFINED; int pos = 0; char state = 'b'; @@ -141,7 +158,12 @@ public class DocViewProperty { break; case 't': if (c == '}') { - type = PropertyType.valueFromName(tmp.toString()); + if (BINARY_REF.equals(tmp.toString())) { + type = PropertyType.BINARY; + isBinaryRef = true; + } else { + type = PropertyType.valueFromName(tmp.toString()); + } tmp.setLength(0); state = 'a'; } else { @@ -212,7 +234,7 @@ public class DocViewProperty { } else { values = new String[]{tmp.toString()}; } - return new DocViewProperty(name, values, isMulti, type); + return new DocViewProperty(name, values, isMulti, type, isBinaryRef); } /** * Formats the given jcr property to the enhanced docview syntax. @@ -221,7 +243,7 @@ public class DocViewProperty { * @throws RepositoryException if a repository error occurs */ public static String format(Property prop) throws RepositoryException { - return format(prop, false); + return format(prop, false, false); } /** @@ -231,14 +253,31 @@ public class DocViewProperty { * @return the formatted string * @throws RepositoryException if a repository error occurs */ - public static String format(Property prop, boolean sort) + public static String format(Property prop, boolean sort, boolean useBinaryReferences) throws RepositoryException { + StringBuffer attrValue = new StringBuffer(); int type = prop.getType(); if (type == PropertyType.BINARY || isAmbiguous(prop)) { - attrValue.append("{"); - attrValue.append(PropertyType.nameFromValue(prop.getType())); - attrValue.append("}"); + + String referenceBinary = null; + if (useBinaryReferences && type == PropertyType.BINARY) { + Binary bin = prop.getBinary(); + if (bin != null && bin instanceof ReferenceBinary) { + referenceBinary = ((ReferenceBinary) bin).getReference(); + } + } + + if (referenceBinary == null) { + attrValue.append("{"); + attrValue.append(PropertyType.nameFromValue(prop.getType())); + attrValue.append("}"); + } else { + attrValue.append("{"); + attrValue.append(BINARY_REF); + attrValue.append("}"); + attrValue.append(referenceBinary); + } } // only write values for non binaries if (prop.getType() != PropertyType.BINARY) { @@ -361,6 +400,16 @@ public class DocViewProperty { return true; } else { Value v = prop == null ? null : prop.getValue(); + + if (type == PropertyType.BINARY && isRef) { + + ReferenceBinary ref = new SimpleReferenceBinary(values[0]); + Binary binary = node.getSession().getValueFactory().createValue(ref).getBinary(); + node.setProperty(name, binary); + + return true; + } + if (v == null || !v.getString().equals(values[0])) { try { if (type == PropertyType.UNDEFINED) { diff --git vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestBinarylessExport.java vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestBinarylessExport.java new file mode 100644 index 0000000..a18e534 --- /dev/null +++ vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/TestBinarylessExport.java @@ -0,0 +1,166 @@ +/* + * 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 org.apache.commons.io.IOUtils; +import org.apache.jackrabbit.api.ReferenceBinary; +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.vault.fs.api.PathFilterSet; +import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf; +import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; +import org.apache.jackrabbit.vault.packaging.ExportOptions; +import org.apache.jackrabbit.vault.packaging.PackageException; +import org.apache.jackrabbit.vault.packaging.PackageProperties; +import org.apache.jackrabbit.vault.packaging.VaultPackage; +import org.junit.Before; +import org.junit.Test; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * TestEmptyPackage... + */ +public class TestBinarylessExport extends IntegrationTestBase { + + private final String BIG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vel dui in elit venenatis dictum sed nec arcu. Phasellus aliquam imperdiet tincidunt. Vestibulum lacinia mollis mi. Cras non metus."; + private final String SMALL_TEXT = "Lorem ipsum"; + private final String BINARY_NODE_PATH = "/tmp/binaryless/node"; + private final String BIG_BINARY_PROPERTY = "bigbin"; + private final String SMALL_BINARY_PROPERTY = "smallbin"; + + private final String FILE_NODE_PATH = "/tmp/binaryless/file"; + + + + + @Before + public void setup() throws RepositoryException, PackageException, IOException { + + Node binaryNode = JcrUtils.getOrCreateByPath(BINARY_NODE_PATH, "nt:unstructured", admin); + + Binary bigBin = admin.getValueFactory().createBinary(IOUtils.toInputStream(BIG_TEXT, "UTF-8")); + Property bigProperty = binaryNode.setProperty(BIG_BINARY_PROPERTY, bigBin); + String referenceBigBinary = ((ReferenceBinary) bigProperty.getBinary()).getReference(); + assertNotNull(referenceBigBinary); + + Binary smallBin = admin.getValueFactory().createBinary(IOUtils.toInputStream(SMALL_TEXT, "UTF-8")); + Property smallProperty = binaryNode.setProperty(SMALL_BINARY_PROPERTY, smallBin); + assertFalse(smallProperty.getBinary() instanceof ReferenceBinary); + + + JcrUtils.putFile(binaryNode.getParent(), "file", "text/plain", IOUtils.toInputStream(BIG_TEXT, "UTF-8")); + admin.save(); + } + + + @Test + public void exportBinary() throws RepositoryException, IOException, PackageException { + + String nodePath = BINARY_NODE_PATH; + String property = BIG_BINARY_PROPERTY; + + ExportOptions opts = new ExportOptions(); + DefaultMetaInf inf = new DefaultMetaInf(); + DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); + filter.add(new PathFilterSet(nodePath)); + + inf.setFilter(filter); + Properties props = new Properties(); + props.setProperty(VaultPackage.NAME_GROUP, "jackrabbit/test"); + props.setProperty(VaultPackage.NAME_NAME, "test-package"); + props.setProperty(PackageProperties.NAME_USE_BINARY_REFERENCES, "true"); + inf.setProperties(props); + + opts.setMetaInf(inf); + + File tmpFile = File.createTempFile("vaulttest", "zip"); + VaultPackage pkg = packMgr.assemble(admin, opts, tmpFile); + + assertNull(pkg.getArchive().getEntry("jcr_root" + BINARY_NODE_PATH + "/" + BIG_BINARY_PROPERTY + ".binary")); + assertNotNull(pkg.getArchive().getEntry("jcr_root"+ BINARY_NODE_PATH + "/" + SMALL_BINARY_PROPERTY + ".binary")); + + + admin.getNode(nodePath).remove(); + + pkg.extract(admin, getDefaultOptions()); + + assertNodeExists(nodePath); + + long actualBinarySize = ((Property) admin.getItem(nodePath + "/" + property)).getBinary().getSize(); + + assertEquals(BIG_TEXT.getBytes("UTF-8").length, actualBinarySize); + + pkg.close(); + tmpFile.delete(); + } + + + @Test + public void exportFile() throws RepositoryException, IOException, PackageException { + + String nodePath = FILE_NODE_PATH; + + ExportOptions opts = new ExportOptions(); + DefaultMetaInf inf = new DefaultMetaInf(); + DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); + filter.add(new PathFilterSet(nodePath)); + + inf.setFilter(filter); + Properties props = new Properties(); + props.setProperty(VaultPackage.NAME_GROUP, "jackrabbit/test"); + props.setProperty(VaultPackage.NAME_NAME, "test-package"); + props.setProperty(PackageProperties.NAME_USE_BINARY_REFERENCES, "true"); + inf.setProperties(props); + + opts.setMetaInf(inf); + + File tmpFile = File.createTempFile("vaulttest", "zip"); + VaultPackage pkg = packMgr.assemble(admin, opts, tmpFile); + + assertTrue(pkg.getArchive().getEntry("jcr_root" + FILE_NODE_PATH).isDirectory()); + + admin.getNode(nodePath).remove(); + + pkg.extract(admin, getDefaultOptions()); + + assertNodeExists(nodePath); + + Node node = admin.getNode(nodePath); + + InputStream stream = JcrUtils.readFile(node); + + String actualText = IOUtils.toString(stream, "UTF-8"); + assertEquals(BIG_TEXT, actualText); + + pkg.close(); + tmpFile.delete(); + } +} \ No newline at end of file