szetszwo commented on code in PR #9886:
URL: https://github.com/apache/ozone/pull/9886#discussion_r3290036332


##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmMultipartPartKey.java:
##########
@@ -0,0 +1,233 @@
+/*
+ * 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.hadoop.ozone.om.helpers;
+
+import jakarta.annotation.Nonnull;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import org.apache.hadoop.hdds.utils.db.Codec;
+import org.apache.hadoop.hdds.utils.db.CodecBuffer;
+
+/**
+ * Typed key for multipart parts table.
+ * Key encoding:
+ * <pre>
+ *   uploadId(utf8) + '/' + partNumber(int32, big-endian)
+ * </pre>
+ * Prefix encoding for iteration:
+ * <pre>
+ *   uploadId(utf8) + '/'
+ * </pre>
+ */
+public final class OmMultipartPartKey {
+  private static final byte SEPARATOR = (byte) '/';
+  private static final Codec<OmMultipartPartKey> CODEC =
+      new OmMultipartPartKeyCodec();
+
+  private final String uploadId;
+  private final Integer partNumber;
+
+  private OmMultipartPartKey(String uploadId, Integer partNumber) {
+    this.uploadId = Objects.requireNonNull(uploadId, "uploadId is null");
+    this.partNumber = partNumber;
+  }
+
+  public static OmMultipartPartKey of(String uploadId, Integer partNumber) {
+    Objects.requireNonNull(partNumber, "partNumber is null");
+    return new OmMultipartPartKey(uploadId, partNumber);
+  }
+
+  public static OmMultipartPartKey prefix(String uploadId) {
+    return new OmMultipartPartKey(uploadId, null);
+  }
+
+  public static Codec<OmMultipartPartKey> getCodec() {
+    return CODEC;
+  }
+
+  public String getUploadId() {
+    return uploadId;
+  }
+
+  public Integer getPartNumber() {
+    return partNumber;
+  }
+
+  public boolean hasPartNumber() {
+    return partNumber != null;
+  }
+
+  @Override
+  public String toString() {
+    return hasPartNumber() ? uploadId + "/" + partNumber : uploadId;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof OmMultipartPartKey)) {
+      return false;
+    }
+    OmMultipartPartKey that = (OmMultipartPartKey) o;
+    return Objects.equals(uploadId, that.uploadId)
+        && Objects.equals(partNumber, that.partNumber);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(uploadId, partNumber);
+  }
+
+  private static final class OmMultipartPartKeyCodec
+      implements Codec<OmMultipartPartKey> {
+
+    @Override
+    public Class<OmMultipartPartKey> getTypeClass() {
+      return OmMultipartPartKey.class;
+    }
+
+    @Override
+    public boolean supportCodecBuffer() {
+      return true;
+    }
+
+    @Override
+    public CodecBuffer toCodecBuffer(
+        @Nonnull OmMultipartPartKey key, CodecBuffer.Allocator allocator) {
+      byte[] uploadBytes = key.uploadId.getBytes(StandardCharsets.UTF_8);
+      int size = uploadBytes.length + 1
+          + (key.hasPartNumber() ? Integer.BYTES : 0);
+      CodecBuffer buffer = allocator.apply(size);
+      buffer.put(ByteBuffer.wrap(uploadBytes)).put(SEPARATOR);
+      if (key.hasPartNumber()) {
+        buffer.putInt(key.partNumber);
+      }
+      return buffer;
+    }
+
+    @Override
+    public OmMultipartPartKey fromCodecBuffer(@Nonnull CodecBuffer buffer)
+        throws IllegalArgumentException {
+      return fromByteBuffer(buffer.asReadOnlyByteBuffer());
+    }
+
+    /**
+     * Encodes the OmMultipartPartKey object into a byte array for storage in 
the key/value store.
+     * Key format:
+     *   prefix key: uploadId + '/'
+     *   full key:   uploadId + '/' + int32(partNumber)
+     * @param key The original java object. Should not be null.
+     * @return Byte array representation of the object for storage in the 
key/value store.
+     */
+    @Override
+    public byte[] toPersistedFormat(OmMultipartPartKey key) {
+      byte[] uploadBytes = key.uploadId.getBytes(StandardCharsets.UTF_8);
+      int size = uploadBytes.length + 1
+          + (key.hasPartNumber() ? Integer.BYTES : 0);
+      ByteBuffer buffer = ByteBuffer.allocate(size);
+      buffer.put(uploadBytes);
+      buffer.put(SEPARATOR);
+      if (key.hasPartNumber()) {
+        buffer.putInt(key.partNumber);
+      }
+      return buffer.array();
+    }
+
+    /**
+     * Decodes the raw byte array from the key/value store into an 
OmMultipartPartKey object.
+     * @param rawData Byte array from the key/value store. Should not be null.
+     * @return OmMultipartPartKey object represented by the raw byte array.
+     * @throws IllegalArgumentException if the rawData format is invalid
+     */
+    @Override
+    public OmMultipartPartKey fromPersistedFormat(byte[] rawData) throws 
IllegalArgumentException {
+      return fromByteBuffer(ByteBuffer.wrap(rawData));
+    }
+
+    private OmMultipartPartKey fromByteBuffer(ByteBuffer rawData)
+        throws IllegalArgumentException {
+      final ByteBuffer input = rawData.asReadOnlyBuffer();
+      final int start = input.position();
+      final int length = input.remaining();
+      if (length == 0) {
+        throw new IllegalArgumentException(
+            "Invalid multipart part key: empty key");
+      }
+
+      //   prefix key: uploadId + '/'
+      //   full key:   uploadId + '/' + int32(partNumber)
+      int suffixLength = getSuffixLength(input, start, length);
+
+      int separatorIndex = start + length - suffixLength - 1;
+      if (separatorIndex < start) {
+        throw new IllegalArgumentException(
+            "Invalid multipart part key: invalid separator position");
+      }
+      final ByteBuffer uploadIdBuffer = input.duplicate();
+      uploadIdBuffer.limit(separatorIndex);
+      uploadIdBuffer.position(start);
+      String uploadId = 
StandardCharsets.UTF_8.decode(uploadIdBuffer).toString();

Review Comment:
   @spacemonkd , We should use StringCodec since UTF_8.decode(..) below uses 
CodingErrorAction.REPLACE.  When there is an error, it will silently replace 
characters and cause unexpected behavior.  
   
   When there is an error, the correct behavior is to throw an exception.  
Filed HDDS-15348.
   
   ```java
   // java.nio.charset.Charset.java
       public final CharBuffer decode(ByteBuffer bb) {
           try {
               return ThreadLocalCoders.decoderFor(this)
                   .onMalformedInput(CodingErrorAction.REPLACE)
                   .onUnmappableCharacter(CodingErrorAction.REPLACE)
                   .decode(bb);
           } catch (CharacterCodingException x) {
               throw new Error(x);         // Can't happen
           }
       }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to