diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/BinaryReferenceLoader.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/BinaryReferenceLoader.java new file mode 100644 index 0000000..0b3313b --- /dev/null +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/BinaryReferenceLoader.java @@ -0,0 +1,93 @@ +package org.apache.jackrabbit.oak.upgrade; + +import org.apache.jackrabbit.api.ReferenceBinary; +import org.apache.jackrabbit.core.data.AbstractDataStore; +import org.apache.jackrabbit.core.data.DataIdentifier; +import org.apache.jackrabbit.core.data.DataStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * The BinaryReferenceLoader attempts to optimize the retrieval of + * binary references by relying on internals of + * + * There is a fallback to the default functionality if the binary is not a + * {@code BLOBInDataStore}. + *
+ * In the optimized the reference is calculated from the binary's + * {@code DataIdentifier}. + */ +public class BinaryReferenceLoader { + + public static final BinaryReferenceLoader INSTANCE = create(); + + private static final Logger LOG = LoggerFactory.getLogger(BinaryReferenceLoader.class); + + private Method getReferenceFromIdentifier; + + private final Field storeField; + + private final Field identifierField; + + public BinaryReferenceLoader(Method getReferenceFromIdentifier, Field storeField, Field identifierField) { + this.getReferenceFromIdentifier = getReferenceFromIdentifier; + this.storeField = storeField; + this.identifierField = identifierField; + } + + private static BinaryReferenceLoader create() { + try { + final Method getReferenceFromIdentifier = AbstractDataStore.class + .getDeclaredMethod("getReferenceFromIdentifier", DataIdentifier.class); + getReferenceFromIdentifier.setAccessible(true); + + final ClassLoader classLoader = BinaryReferenceLoader.class.getClassLoader(); + final Class binaryClass = classLoader.loadClass("org.apache.jackrabbit.core.value.BLOBInDataStore"); + final Field storeField = binaryClass.getDeclaredField("store"); + storeField.setAccessible(true); + final Field identifierField = binaryClass.getDeclaredField("identifier"); + identifierField.setAccessible(true); + return new BinaryReferenceLoader(getReferenceFromIdentifier, storeField, identifierField); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Failed to create BinaryReferenceLoader", e); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Failed to create BinaryReferenceLoader", e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Failed to create BinaryReferenceLoader", e); + } + } + + public String getBinaryReference(ReferenceBinary binary) { + if ("org.apache.jackrabbit.core.value.BLOBInDataStore".equals(binary.getClass().getName())) { + try { + final DataStore dataStore = (DataStore) storeField.get(binary); + final DataIdentifier identifier = (DataIdentifier) identifierField.get(binary); + return getReferenceFromIdentifier(dataStore, identifier); + } catch (IllegalAccessException e) { + LOG.error("failed optimization: ", e); + // fall through + } + } + return binary.getReference(); + } + + public String getReferenceFromIdentifier(DataStore dataStore, DataIdentifier identifier) { + try { + return (String) getReferenceFromIdentifier.invoke(dataStore, identifier); + } catch (IllegalAccessException e) { + LOG.error("failed optimization: ", e); + // fall through + } catch (InvocationTargetException e) { + LOG.error("failed optimization: ", e); + // fall through + } + return null; + } +} diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java index 46e9fc8..c9ef4db 100644 --- oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java @@ -104,14 +104,16 @@ class JackrabbitNodeState extends AbstractNodeState { private String path; /** * Bundle loader based on the source persistence manager. */ private final BundleLoader loader; + private final BinaryReferenceLoader binaryReferenceLoader; + private final String workspaceName; private final TypePredicate isReferenceable; private final TypePredicate isOrderable; private final TypePredicate isVersionable; @@ -135,14 +137,15 @@ class JackrabbitNodeState extends AbstractNodeState { private JackrabbitNodeState( JackrabbitNodeState parent, String name, NodePropBundle bundle) { this.parent = parent; this.name = name; this.path = null; this.loader = parent.loader; + this.binaryReferenceLoader = parent.binaryReferenceLoader; this.workspaceName = parent.workspaceName; this.isReferenceable = parent.isReferenceable; this.isOrderable = parent.isOrderable; this.isVersionable = parent.isVersionable; this.isVersionHistory = parent.isVersionHistory; this.isFrozenNode = parent.isFrozenNode; this.uriToPrefix = parent.uriToPrefix; @@ -161,14 +164,15 @@ class JackrabbitNodeState extends AbstractNodeState { Map uriToPrefix, NodeId id, String path, String workspaceName, Map versionablePaths, boolean useBinaryReferences) { this.parent = null; this.name = null; this.path = path; this.loader = new BundleLoader(source); + this.binaryReferenceLoader = BinaryReferenceLoader.INSTANCE; this.workspaceName = workspaceName; this.isReferenceable = new TypePredicate(root, MIX_REFERENCEABLE); this.isOrderable = TypePredicate.isOrderable(root); this.isVersionable = new TypePredicate(root, MIX_VERSIONABLE); this.isVersionHistory = new TypePredicate(root, NT_VERSIONHISTORY); this.isFrozenNode = new TypePredicate(root, NT_FROZENNODE); this.uriToPrefix = uriToPrefix; @@ -549,26 +553,50 @@ class JackrabbitNodeState extends AbstractNodeState { if (!useBinaryReferences) { return null; } try { Binary binary = value.getBinary(); try { if (binary instanceof ReferenceBinary) { + if (binaryReferenceLoader != null) { + return binaryReferenceLoader.getBinaryReference((ReferenceBinary) binary); + } return ((ReferenceBinary) binary).getReference(); } else { return null; } } finally { binary.dispose(); } } catch (RepositoryException e) { warn("Unable to get blob reference", e); return null; } } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Blob)) { + return false; + } + + // make sure reference comparison happens early + final Blob that = (Blob) other; + final String referenceA = this.getReference(); + final String referenceB = that.getReference(); + if (referenceA != null && referenceB != null) { + return referenceA.equals(referenceB); + } + + return super.equals(that); + } }; } private String createName(Name name) { String uri = name.getNamespaceURI(); String local = name.getLocalName(); if (uri == null || uri.isEmpty()) { diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/ReferenceOptimizedBlobStore.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/ReferenceOptimizedBlobStore.java new file mode 100644 index 0000000..a35969d --- /dev/null +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/ReferenceOptimizedBlobStore.java @@ -0,0 +1,33 @@ +package org.apache.jackrabbit.oak.upgrade; + +import org.apache.jackrabbit.core.data.DataIdentifier; +import org.apache.jackrabbit.core.data.DataStore; +import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore; + +public class ReferenceOptimizedBlobStore extends DataStoreBlobStore { + + private final BinaryReferenceLoader binaryReferenceLoader; + + private DataStore dataStore; + + public ReferenceOptimizedBlobStore(DataStore dataStore) { + super(dataStore); + this.dataStore = dataStore; + this.binaryReferenceLoader = BinaryReferenceLoader.INSTANCE; + } + + @Override + public String getReference(String encodedBlobId) { + String blobId = extractBlobId(encodedBlobId); + final DataIdentifier identifier = new DataIdentifier(blobId); + return binaryReferenceLoader.getReferenceFromIdentifier(dataStore, identifier); + } + + private String extractBlobId(String encodedBlobId) { + int indexOfSep = encodedBlobId.lastIndexOf("#"); + if (indexOfSep != -1) { + return encodedBlobId.substring(0, indexOfSep); + } + return encodedBlobId; + } +}