diff --git a/core/src/main/java/org/dcache/nfs/util/Opaque.java b/core/src/main/java/org/dcache/nfs/util/Opaque.java index 119d4fdd..bec1ddba 100644 --- a/core/src/main/java/org/dcache/nfs/util/Opaque.java +++ b/core/src/main/java/org/dcache/nfs/util/Opaque.java @@ -22,24 +22,43 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Base64; +import java.util.Objects; /** - * Describes something that can be used as a key in {@link java.util.Map} and that can be converted to a {@code byte[]} - * and a Base64 string representation. + * Describes something that can be used as a key for {@link java.util.HashMap} and that can be converted to a + * {@code byte[]} and a Base64 string representation. + *
+ * Note that {@link Opaque}s that are stored in {@link java.util.HashMap} need to be immutable. Call + * {@link #toImmutableOpaque()} when necessary (e.g., when using {@link java.util.HashMap#put(Object, Object)}, + * {@link java.util.HashMap#computeIfAbsent(Object, java.util.function.Function)}, etc. */ public interface Opaque { /** - * Returns an {@link Opaque} instance based on a copy of the given bytes. + * Returns an immutable {@link Opaque} instance based on a copy of the given bytes. * * @param bytes The bytes. * @return The {@link Opaque} instance. */ static Opaque forBytes(byte[] bytes) { - return new OpaqueImpl(bytes.clone()); + return new OpaqueImmutableImpl(bytes.clone()); } /** - * Returns an {@link Opaque} instance based on a copy of the {@code length} bytes from the given {@link ByteBuffer}. + * Returns an mutable {@link Opaque} instance based on the given byte array. + *
+ * Note that the returned {@link Opaque} is typically not suitable for storing in a + * {@link java.util.HashMap}, but merely for lookups. Call {@link #toImmutableOpaque()} when necessary. + * + * @param bytes The bytes. + * @return The {@link Opaque} instance. + */ + static Opaque forMutableByteArray(byte[] bytes) { + return new OpaqueImpl(bytes); + } + + /** + * Returns an immutable {@link Opaque} instance based on a copy of the {@code length} bytes from the given + * {@link ByteBuffer}. * * @param buf The buffer. * @param length The number of bytes. @@ -49,7 +68,24 @@ static Opaque forBytes(ByteBuffer buf, int length) { byte[] bytes = new byte[length]; buf.get(bytes); - return new OpaqueImpl(bytes); + return new OpaqueImmutableImpl(bytes); + } + + /** + * Returns a mutable {@link Opaque} instance backed on the byte contents of the given {@link ByteBuffer}, + * for the given number of bytes starting from the given absolute index. + *
+ * Note that the returned {@link Opaque} is typically not suitable for storing in a
+ * {@link java.util.HashMap}, but merely for lookups. Call {@link #toImmutableOpaque()} when necessary.
+ *
+ * @param buf The buffer backing the {@link Opaque}.
+ * @param index The absolute index to start from.
+ * @param length The number of bytes.
+ * @return The {@link Opaque} instance.
+ * @see #toImmutableOpaque()
+ */
+ static Opaque forMutableByteBuffer(ByteBuffer buf, int index, int length) {
+ return new OpaqueBufferImpl(buf, index, length);
}
/**
@@ -102,6 +138,13 @@ static boolean defaultEquals(Opaque obj, Object other) {
*/
String toBase64();
+ /**
+ * Returns an immutable {@link Opaque}, which may be the instance itself if it is already immutable.
+ *
+ * @return An immutable opaque.
+ */
+ Opaque toImmutableOpaque();
+
/**
* Writes the bytes of this {@link Opaque} to the given {@link ByteBuffer}.
*
@@ -131,11 +174,10 @@ default void putBytes(ByteBuffer buf) {
@Override
boolean equals(Object o);
- final class OpaqueImpl implements Opaque {
- private final byte[] _opaque;
- private String base64 = null;
+ class OpaqueImpl implements Opaque {
+ final byte[] _opaque;
- private OpaqueImpl(byte[] opaque) {
+ OpaqueImpl(byte[] opaque) {
_opaque = opaque;
}
@@ -145,21 +187,22 @@ public byte[] toBytes() {
}
@Override
- public String toBase64() {
- if (base64 == null) {
- base64 = Base64.getEncoder().withoutPadding().encodeToString(_opaque);
- }
- return base64;
+ public int hashCode() {
+ return Arrays.hashCode(_opaque);
}
@Override
- public void putBytes(ByteBuffer buf) {
- buf.put(_opaque);
+ public String toBase64() {
+ return toBase64Impl();
+ }
+
+ protected String toBase64Impl() {
+ return Base64.getEncoder().withoutPadding().encodeToString(_opaque);
}
@Override
- public int hashCode() {
- return Arrays.hashCode(_opaque);
+ public void putBytes(ByteBuffer buf) {
+ buf.put(_opaque);
}
@Override
@@ -173,6 +216,19 @@ public boolean equals(Object o) {
if (o instanceof OpaqueImpl) {
return Arrays.equals(_opaque, ((OpaqueImpl) o)._opaque);
+ } else if (o instanceof OpaqueBufferImpl) {
+ OpaqueBufferImpl other = (OpaqueBufferImpl) o;
+ if (other.numBytes() != _opaque.length) {
+ return false;
+ }
+ ByteBuffer otherBuf = other.buf;
+ int otherIndex = other.index;
+ for (int i = 0, n = _opaque.length, oi = otherIndex; i < n; i++, oi++) {
+ if (_opaque[i] != otherBuf.get(oi)) {
+ return false;
+ }
+ }
+ return true;
} else {
return Arrays.equals(_opaque, ((Opaque) o).toBytes());
}
@@ -192,5 +248,125 @@ public String toString() {
public int numBytes() {
return _opaque.length;
}
+
+ @Override
+ public Opaque toImmutableOpaque() {
+ return Opaque.forBytes(_opaque);
+ }
+ }
+
+ final class OpaqueImmutableImpl extends OpaqueImpl {
+ private String base64 = null;
+ private int hashCode;
+
+ protected OpaqueImmutableImpl(byte[] opaque) {
+ super(opaque);
+ }
+
+ @Override
+ public int hashCode() {
+ if (hashCode == 0) {
+ hashCode = Arrays.hashCode(_opaque);
+ }
+ return hashCode;
+ }
+
+ @Override
+ public String toBase64() {
+ if (base64 == null) {
+ base64 = toBase64Impl();
+ }
+ return base64;
+ }
+
+ @Override
+ public Opaque toImmutableOpaque() {
+ return this;
+ }
+ }
+
+ final class OpaqueBufferImpl implements Opaque {
+ private final ByteBuffer buf;
+ private final int index;
+ private final int length;
+
+ private OpaqueBufferImpl(ByteBuffer buf, int index, int length) {
+ this.buf = Objects.requireNonNull(buf);
+ this.index = index;
+ this.length = length;
+ }
+
+ @Override
+ public byte[] toBytes() {
+ byte[] bytes = new byte[length];
+ buf.get(index, bytes);
+ return bytes;
+ }
+
+ @Override
+ public int numBytes() {
+ return length;
+ }
+
+ @Override
+ public String toBase64() {
+ return Base64.getEncoder().withoutPadding().encodeToString(toBytes());
+ }
+
+ @Override
+ public Opaque toImmutableOpaque() {
+ return Opaque.forBytes(toBytes());
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ for (int i = index, n = index + length; i < n; i++) {
+ byte element = buf.get(i);
+ result = 31 * result + element;
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Opaque)) {
+ return false;
+ }
+ if (length != ((Opaque) o).numBytes()) {
+ return false;
+ }
+
+ if (o instanceof OpaqueImpl) {
+ byte[] otherBytes = ((OpaqueImpl) o)._opaque;
+ for (int i = index, n = index + length, oi = 0; i < n; i++, oi++) {
+ if (buf.get(i) != otherBytes[oi]) {
+ return false;
+ }
+ }
+ return true;
+ } else if (o instanceof OpaqueBufferImpl) {
+ OpaqueBufferImpl other = (OpaqueBufferImpl) o;
+ ByteBuffer otherBuf = other.buf;
+ int otherIndex = other.index;
+ for (int i = index, n = index + length, oi = otherIndex; i < n; i++, oi++) {
+ if (buf.get(i) != otherBuf.get(oi)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return toImmutableOpaque().equals(o);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "[" + toBase64() + "]";
+ }
}
}
diff --git a/core/src/main/java/org/dcache/nfs/v4/FileTracker.java b/core/src/main/java/org/dcache/nfs/v4/FileTracker.java
index e6e5991c..1ff0b9a4 100644
--- a/core/src/main/java/org/dcache/nfs/v4/FileTracker.java
+++ b/core/src/main/java/org/dcache/nfs/v4/FileTracker.java
@@ -242,7 +242,7 @@ public OpenRecord addOpen(NFS4Client client, StateOwner owner, Inode inode, int
// client explicitly requested write delegation
boolean wantWriteDelegation = (shareAccess & nfs4_prot.OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG) != 0;
- Opaque fileId = inode.getFileIdKey();
+ final Opaque fileId = inode.getFileIdKey().toImmutableOpaque();
Lock lock = filesLock.get(fileId);
lock.lock();
try {
diff --git a/core/src/main/java/org/dcache/nfs/v4/nlm/SimpleLm.java b/core/src/main/java/org/dcache/nfs/v4/nlm/SimpleLm.java
index 0d1aa92d..71b1b676 100644
--- a/core/src/main/java/org/dcache/nfs/v4/nlm/SimpleLm.java
+++ b/core/src/main/java/org/dcache/nfs/v4/nlm/SimpleLm.java
@@ -58,36 +58,32 @@ public SimpleLm(int concurrency) {
/**
* Exclusive lock on objects locks.
*/
- private final ConcurrentHashMap