You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@ozone.apache.org by "echonesis (via GitHub)" <gi...@apache.org> on 2025/12/17 07:08:47 UTC

[PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

echonesis opened a new pull request, #9516:
URL: https://github.com/apache/ozone/pull/9516

   ## What changes were proposed in this pull request?
   Refactored the BucketEndpoint#put method by extracting ACL handling logic into dedicated handler classes, improving code modularity and maintainability through the Strategy pattern.
   
   ### Changes
   - Created `AclHandler` class to handle bucket ACL PUT operations
   - Added `BucketOperationHandler` interface for pluggable operation handlers
   - Implemented `BucketOperationHandlerFactory` to route requests to appropriate handlers
   - Introduced `BucketEndpointContext` to provide handler access to bucket/volume operations
   - Refactored `BucketEndpoint#put()` to delegate ACL operations to `AclHandler`
   
   ### Testing
   - Added `TestAclHandler` 
   - Added `TestBucketEndpointContext`
   - Added `TestBucketOperationHandlerFactory` 
   - Updated existing `TestBucketAcl` tests
   
   ## What is the link to the Apache JIRA
   
   https://issues.apache.org/jira/browse/HDDS-14123
   
   ## How was this patch tested?
   
   GitHub Actions CI: https://github.com/echonesis/ozone/actions/runs/20292867020
   


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

Posted by "adoroszlai (via GitHub)" <gi...@apache.org>.
adoroszlai commented on code in PR #9516:
URL: https://github.com/apache/ozone/pull/9516#discussion_r2662123006


##########
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestAclHandler.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * 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.s3.endpoint;
+
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for AclHandler.
+ */
+public class TestAclHandler {
+
+  private static final String BUCKET_NAME = OzoneConsts.S3_BUCKET;
+  private OzoneClient client;
+  private AclHandler aclHandler;
+  private HttpHeaders headers;
+
+  @BeforeEach
+  public void setup() throws IOException {
+    client = new OzoneClientStub();
+    client.getObjectStore().createS3Bucket(BUCKET_NAME);
+
+    headers = mock(HttpHeaders.class);
+
+    // Build AclHandler using EndpointBuilder since it extends EndpointBase
+    aclHandler = EndpointBuilder.newAclHandlerBuilder()
+        .setClient(client)
+        .setHeaders(headers)
+        .build();
+  }
+
+  @AfterEach
+  public void clean() throws IOException {
+    if (client != null) {
+      client.close();
+    }
+  }
+
+  @Test
+  public void testHandlePutRequestWithAclQueryParam() throws Exception {
+    // Set up query parameter to indicate ACL operation
+    aclHandler.queryParamsForTest().set("acl", "");

Review Comment:
   This can be added in `setup()` to reduce duplication, then unset only in `testHandlePutRequestWithoutAclQueryParam`.



##########
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestAclHandler.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * 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.s3.endpoint;
+
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for AclHandler.
+ */
+public class TestAclHandler {
+
+  private static final String BUCKET_NAME = OzoneConsts.S3_BUCKET;
+  private OzoneClient client;
+  private AclHandler aclHandler;
+  private HttpHeaders headers;
+
+  @BeforeEach
+  public void setup() throws IOException {
+    client = new OzoneClientStub();
+    client.getObjectStore().createS3Bucket(BUCKET_NAME);
+
+    headers = mock(HttpHeaders.class);
+
+    // Build AclHandler using EndpointBuilder since it extends EndpointBase
+    aclHandler = EndpointBuilder.newAclHandlerBuilder()
+        .setClient(client)
+        .setHeaders(headers)
+        .build();
+  }
+
+  @AfterEach
+  public void clean() throws IOException {
+    if (client != null) {
+      client.close();
+    }
+  }
+
+  @Test
+  public void testHandlePutRequestWithAclQueryParam() throws Exception {
+    // Set up query parameter to indicate ACL operation
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNotNull(response, "Handler should handle request with ?acl param");
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithoutAclQueryParam() throws Exception {
+    // No "acl" query parameter - handler should not handle request
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNull(response, "Handler should return null without ?acl param");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithWriteHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadAcpHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ_ACP))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithWriteAcpHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE_ACP))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithFullControlHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithMultipleHeaders() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser1\"");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"testuser2\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL with multiple headers should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithUnsupportedGranteeType() {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("uri=\"http://example.com\"");
+
+    OS3Exception exception = assertThrows(OS3Exception.class, () -> {
+      aclHandler.handlePutRequest(BUCKET_NAME, null);
+    }, "Should throw OS3Exception for unsupported grantee type");
+
+    assertEquals(HTTP_NOT_IMPLEMENTED, exception.getHttpCode(),
+        "Should return NOT_IMPLEMENTED for unsupported grantee type");
+  }
+
+  @Test
+  public void testHandlePutRequestWithEmailAddressType() {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("emailAddress=\"test@example.com\"");
+
+    OS3Exception exception = assertThrows(OS3Exception.class, () -> {
+      aclHandler.handlePutRequest(BUCKET_NAME, null);
+    }, "Should throw OS3Exception for email address grantee type");
+
+    assertEquals(HTTP_NOT_IMPLEMENTED, exception.getHttpCode(),
+        "Should return NOT_IMPLEMENTED for email address grantee type");
+  }
+
+  @Test
+  public void testHandlePutRequestBucketNotFound() {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    assertThrows(OS3Exception.class, () -> {
+      aclHandler.handlePutRequest("nonexistent-bucket", null);
+    }, "Should throw OS3Exception for non-existent bucket");
+  }
+
+  @Test
+  public void testHandlePutRequestWithBody() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    String aclXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+        "<AccessControlPolicy xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n" +
+        "  <Owner>\n" +
+        "    <ID>testowner</ID>\n" +
+        "    <DisplayName>Test Owner</DisplayName>\n" +
+        "  </Owner>\n" +
+        "  <AccessControlList>\n" +
+        "    <Grant>\n" +
+        "      <Grantee xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+        "xsi:type=\"CanonicalUser\">\n" +
+        "        <ID>testuser</ID>\n" +
+        "      </Grantee>\n" +
+        "      <Permission>READ</Permission>\n" +
+        "    </Grant>\n" +
+        "  </AccessControlList>\n" +
+        "</AccessControlPolicy>";
+
+    InputStream body = new ByteArrayInputStream(
+        aclXml.getBytes(StandardCharsets.UTF_8));
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, body);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL with body should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithInvalidHeaderFormat() {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("invalid-format");
+
+    assertThrows(OS3Exception.class, () -> {
+      aclHandler.handlePutRequest(BUCKET_NAME, null);
+    }, "Should throw OS3Exception for invalid header format");
+  }
+
+  @Test
+  public void testHandlePutRequestWithMultipleGrantees() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"user1\",id=\"user2\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL with multiple grantees should return 200 OK");
+  }
+
+  @Test
+  public void testPutAclReplacesExistingAcls() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+
+    // Set initial ACL
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"user1\"");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn(null);
+
+    aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    // Replace with new ACL
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn(null);
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"user2\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should replace existing ACLs");
+  }
+
+  @Test
+  public void testAuditLoggingOnBucketNotFound() throws Exception {
+    // Create a spy of AclHandler to verify audit logging
+    AclHandler spyHandler = spy(EndpointBuilder.newAclHandlerBuilder()
+        .setClient(client)
+        .setHeaders(headers)
+        .build());

Review Comment:
   We can `spy(aclHandler)`, no need to create new instance.



##########
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestAclHandler.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * 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.s3.endpoint;
+
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for AclHandler.
+ */
+public class TestAclHandler {
+
+  private static final String BUCKET_NAME = OzoneConsts.S3_BUCKET;
+  private OzoneClient client;
+  private AclHandler aclHandler;
+  private HttpHeaders headers;
+
+  @BeforeEach
+  public void setup() throws IOException {
+    client = new OzoneClientStub();
+    client.getObjectStore().createS3Bucket(BUCKET_NAME);
+
+    headers = mock(HttpHeaders.class);
+
+    // Build AclHandler using EndpointBuilder since it extends EndpointBase
+    aclHandler = EndpointBuilder.newAclHandlerBuilder()
+        .setClient(client)
+        .setHeaders(headers)
+        .build();
+  }
+
+  @AfterEach
+  public void clean() throws IOException {
+    if (client != null) {
+      client.close();
+    }
+  }
+
+  @Test
+  public void testHandlePutRequestWithAclQueryParam() throws Exception {
+    // Set up query parameter to indicate ACL operation
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNotNull(response, "Handler should handle request with ?acl param");
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithoutAclQueryParam() throws Exception {
+    // No "acl" query parameter - handler should not handle request
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNull(response, "Handler should return null without ?acl param");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithWriteHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"testuser\"");

Review Comment:
   `testHandlePutRequestWithReadHeader`, `testHandlePutRequestWithWriteHeader`, `testHandlePutRequestWithReadAcpHeader`, `testHandlePutRequestWithWriteAcpHeader`, `testHandlePutRequestWithFullControlHeader` can be combined into a single `@ParameterizedTest`.



##########
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestAclHandler.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * 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.s3.endpoint;
+
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for AclHandler.
+ */
+public class TestAclHandler {
+
+  private static final String BUCKET_NAME = OzoneConsts.S3_BUCKET;
+  private OzoneClient client;
+  private AclHandler aclHandler;
+  private HttpHeaders headers;
+
+  @BeforeEach
+  public void setup() throws IOException {
+    client = new OzoneClientStub();
+    client.getObjectStore().createS3Bucket(BUCKET_NAME);
+
+    headers = mock(HttpHeaders.class);
+
+    // Build AclHandler using EndpointBuilder since it extends EndpointBase
+    aclHandler = EndpointBuilder.newAclHandlerBuilder()
+        .setClient(client)
+        .setHeaders(headers)
+        .build();
+  }
+
+  @AfterEach
+  public void clean() throws IOException {
+    if (client != null) {
+      client.close();
+    }
+  }
+
+  @Test
+  public void testHandlePutRequestWithAclQueryParam() throws Exception {
+    // Set up query parameter to indicate ACL operation
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNotNull(response, "Handler should handle request with ?acl param");
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithoutAclQueryParam() throws Exception {
+    // No "acl" query parameter - handler should not handle request
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNull(response, "Handler should return null without ?acl param");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");

Review Comment:
   Can simplify a bit:
   
   ```java
   assertSucceeds(() -> aclHandler.handlePutRequest(BUCKET_NAME, null));
   ```



##########
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestAclHandler.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * 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.s3.endpoint;
+
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for AclHandler.
+ */
+public class TestAclHandler {
+
+  private static final String BUCKET_NAME = OzoneConsts.S3_BUCKET;
+  private OzoneClient client;
+  private AclHandler aclHandler;
+  private HttpHeaders headers;
+
+  @BeforeEach
+  public void setup() throws IOException {
+    client = new OzoneClientStub();
+    client.getObjectStore().createS3Bucket(BUCKET_NAME);
+
+    headers = mock(HttpHeaders.class);
+
+    // Build AclHandler using EndpointBuilder since it extends EndpointBase
+    aclHandler = EndpointBuilder.newAclHandlerBuilder()
+        .setClient(client)
+        .setHeaders(headers)
+        .build();
+  }
+
+  @AfterEach
+  public void clean() throws IOException {
+    if (client != null) {
+      client.close();
+    }
+  }
+
+  @Test
+  public void testHandlePutRequestWithAclQueryParam() throws Exception {
+    // Set up query parameter to indicate ACL operation
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNotNull(response, "Handler should handle request with ?acl param");
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithoutAclQueryParam() throws Exception {
+    // No "acl" query parameter - handler should not handle request
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNull(response, "Handler should return null without ?acl param");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithWriteHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadAcpHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ_ACP))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithWriteAcpHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE_ACP))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithFullControlHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithMultipleHeaders() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser1\"");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"testuser2\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL with multiple headers should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithUnsupportedGranteeType() {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("uri=\"http://example.com\"");
+
+    OS3Exception exception = assertThrows(OS3Exception.class, () -> {
+      aclHandler.handlePutRequest(BUCKET_NAME, null);
+    }, "Should throw OS3Exception for unsupported grantee type");

Review Comment:
   nit: `{}` is unnecessary (in all `assertThrows` calls).



##########
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestAclHandler.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * 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.s3.endpoint;
+
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for AclHandler.
+ */
+public class TestAclHandler {
+
+  private static final String BUCKET_NAME = OzoneConsts.S3_BUCKET;
+  private OzoneClient client;
+  private AclHandler aclHandler;
+  private HttpHeaders headers;
+
+  @BeforeEach
+  public void setup() throws IOException {
+    client = new OzoneClientStub();
+    client.getObjectStore().createS3Bucket(BUCKET_NAME);
+
+    headers = mock(HttpHeaders.class);
+
+    // Build AclHandler using EndpointBuilder since it extends EndpointBase
+    aclHandler = EndpointBuilder.newAclHandlerBuilder()
+        .setClient(client)
+        .setHeaders(headers)
+        .build();
+  }
+
+  @AfterEach
+  public void clean() throws IOException {
+    if (client != null) {
+      client.close();
+    }
+  }
+
+  @Test
+  public void testHandlePutRequestWithAclQueryParam() throws Exception {
+    // Set up query parameter to indicate ACL operation
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNotNull(response, "Handler should handle request with ?acl param");
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithoutAclQueryParam() throws Exception {
+    // No "acl" query parameter - handler should not handle request
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertNull(response, "Handler should return null without ?acl param");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithWriteHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithReadAcpHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ_ACP))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithWriteAcpHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE_ACP))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithFullControlHeader() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL))
+        .thenReturn("id=\"testuser\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithMultipleHeaders() throws Exception {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("id=\"testuser1\"");
+    when(headers.getHeaderString(S3Acl.GRANT_WRITE))
+        .thenReturn("id=\"testuser2\"");
+
+    Response response = aclHandler.handlePutRequest(BUCKET_NAME, null);
+
+    assertEquals(HTTP_OK, response.getStatus(),
+        "PUT ACL with multiple headers should return 200 OK");
+  }
+
+  @Test
+  public void testHandlePutRequestWithUnsupportedGranteeType() {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("uri=\"http://example.com\"");
+
+    OS3Exception exception = assertThrows(OS3Exception.class, () -> {
+      aclHandler.handlePutRequest(BUCKET_NAME, null);
+    }, "Should throw OS3Exception for unsupported grantee type");
+
+    assertEquals(HTTP_NOT_IMPLEMENTED, exception.getHttpCode(),
+        "Should return NOT_IMPLEMENTED for unsupported grantee type");
+  }
+
+  @Test
+  public void testHandlePutRequestWithEmailAddressType() {
+    aclHandler.queryParamsForTest().set("acl", "");
+    when(headers.getHeaderString(S3Acl.GRANT_READ))
+        .thenReturn("emailAddress=\"test@example.com\"");
+
+    OS3Exception exception = assertThrows(OS3Exception.class, () -> {
+      aclHandler.handlePutRequest(BUCKET_NAME, null);
+    }, "Should throw OS3Exception for email address grantee type");
+
+    assertEquals(HTTP_NOT_IMPLEMENTED, exception.getHttpCode(),
+        "Should return NOT_IMPLEMENTED for email address grantee type");

Review Comment:
   These can be simplified to:
   
   ```java
   assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED,
       () -> aclHandler.handlePutRequest(BUCKET_NAME, null));
   ```



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

Posted by "adoroszlai (via GitHub)" <gi...@apache.org>.
adoroszlai commented on code in PR #9516:
URL: https://github.com/apache/ozone/pull/9516#discussion_r2660861488


##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java:
##########
@@ -96,6 +90,8 @@ public class BucketEndpoint extends EndpointBase {
   private boolean listKeysShallowEnabled;
   private int maxKeysLimit = 1000;
 
+  private List<BucketOperationHandler> putHandlers;

Review Comment:
   `BucketOperationHandler` will eventually handle other operations (`get`, etc.), so this should be simply `handlers`.



##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/AclHandler.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * 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.s3.endpoint;
+
+import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.newError;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import javax.annotation.PostConstruct;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.ozone.OzoneAcl;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
+import org.apache.hadoop.ozone.om.helpers.OzoneAclUtil;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.util.Time;
+import org.apache.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler for bucket ACL operations (?acl query parameter).
+ * Implements PUT operations for bucket Access Control Lists.
+ *
+ * This handler extends EndpointBase to inherit all required functionality
+ * (configuration, headers, request context, audit logging, metrics, etc.).
+ */
+public class AclHandler extends EndpointBase implements BucketOperationHandler {
+
+  private static final Logger LOG = LoggerFactory.getLogger(AclHandler.class);
+
+  /**
+   * Determine if this handler should handle the current request.
+   * @return true if the request has the "acl" query parameter
+   */
+  private boolean shouldHandle() {
+    return queryParams().get(QueryParams.ACL) != null;
+  }
+
+  /**
+   * Implement acl put.
+   * <p>
+   * see: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html
+   */
+  @Override
+  public Response handlePutRequest(String bucketName, InputStream body)
+      throws IOException, OS3Exception {
+
+    if (!shouldHandle()) {
+      return null;  // Not responsible for this request
+    }
+
+    long startNanos = Time.monotonicNowNanos();
+    S3GAction s3GAction = S3GAction.PUT_ACL;
+
+    String grantReads = getHeaders().getHeaderString(S3Acl.GRANT_READ);
+    String grantWrites = getHeaders().getHeaderString(S3Acl.GRANT_WRITE);
+    String grantReadACP = getHeaders().getHeaderString(S3Acl.GRANT_READ_ACP);
+    String grantWriteACP = getHeaders().getHeaderString(S3Acl.GRANT_WRITE_ACP);
+    String grantFull = getHeaders().getHeaderString(S3Acl.GRANT_FULL_CONTROL);
+
+    try {
+      OzoneBucket bucket = getBucket(bucketName);
+      S3Owner.verifyBucketOwnerCondition(getHeaders(), bucketName, bucket.getOwner());
+      OzoneVolume volume = getVolume();
+
+      List<OzoneAcl> ozoneAclListOnBucket = new ArrayList<>();
+      List<OzoneAcl> ozoneAclListOnVolume = new ArrayList<>();
+
+      if (grantReads == null && grantWrites == null && grantReadACP == null
+          && grantWriteACP == null && grantFull == null) {
+        // Handle grants in body
+        S3BucketAcl putBucketAclRequest =
+            new PutBucketAclRequestUnmarshaller().readFrom(body);
+        ozoneAclListOnBucket.addAll(
+            S3Acl.s3AclToOzoneNativeAclOnBucket(putBucketAclRequest));
+        ozoneAclListOnVolume.addAll(
+            S3Acl.s3AclToOzoneNativeAclOnVolume(putBucketAclRequest));
+      } else {
+        // Handle grants in headers
+        if (grantReads != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantReads,
+              S3Acl.ACLType.READ.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantReads,
+              S3Acl.ACLType.READ.getValue()));
+        }
+        if (grantWrites != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantWrites,
+              S3Acl.ACLType.WRITE.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantWrites,
+              S3Acl.ACLType.WRITE.getValue()));
+        }
+        if (grantReadACP != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantReadACP,
+              S3Acl.ACLType.READ_ACP.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantReadACP,
+              S3Acl.ACLType.READ_ACP.getValue()));
+        }
+        if (grantWriteACP != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantWriteACP,
+              S3Acl.ACLType.WRITE_ACP.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantWriteACP,
+              S3Acl.ACLType.WRITE_ACP.getValue()));
+        }
+        if (grantFull != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantFull,
+              S3Acl.ACLType.FULL_CONTROL.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantFull,
+              S3Acl.ACLType.FULL_CONTROL.getValue()));
+        }
+      }
+
+      // A put request will reset all previous ACLs on bucket
+      bucket.setAcl(ozoneAclListOnBucket);
+
+      // A put request will reset input user/group's permission on volume
+      List<OzoneAcl> acls = bucket.getAcls();
+      List<OzoneAcl> aclsToRemoveOnVolume = new ArrayList<>();
+      List<OzoneAcl> currentAclsOnVolume = volume.getAcls();
+
+      // Remove input user/group's permission from Volume first
+      if (!currentAclsOnVolume.isEmpty()) {
+        for (OzoneAcl acl : acls) {
+          if (acl.getAclScope() == ACCESS) {
+            aclsToRemoveOnVolume.addAll(OzoneAclUtil.filterAclList(
+                acl.getName(), acl.getType(), currentAclsOnVolume));
+          }
+        }
+        for (OzoneAcl acl : aclsToRemoveOnVolume) {
+          volume.removeAcl(acl);
+        }
+      }
+
+      // Add new permission on Volume
+      for (OzoneAcl acl : ozoneAclListOnVolume) {
+        volume.addAcl(acl);
+      }
+
+      getMetrics().updatePutAclSuccessStats(startNanos);
+      auditWriteSuccess(s3GAction);
+      return Response.status(HttpStatus.SC_OK).build();
+
+    } catch (OMException exception) {
+      getMetrics().updatePutAclFailureStats(startNanos);
+      auditWriteFailure(s3GAction, exception);
+      if (exception.getResult() == ResultCodes.BUCKET_NOT_FOUND) {
+        throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, exception);
+      } else if (isAccessDenied(exception)) {
+        throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, exception);
+      }
+      throw exception;
+    } catch (OS3Exception ex) {
+      getMetrics().updatePutAclFailureStats(startNanos);
+      auditWriteFailure(s3GAction, ex);
+      throw ex;
+    }
+  }
+
+  /**
+   * Convert ACL string to Ozone ACL on bucket.
+   *
+   * Example: x-amz-grant-write: id="111122223333", id="555566667777"
+   */
+  private List<OzoneAcl> getAndConvertAclOnBucket(String value,
+                                                  String permission)
+      throws OS3Exception {
+    return parseAndConvertAcl(value, permission, true);
+  }
+
+  /**
+   * Convert ACL string to Ozone ACL on volume.
+   */
+  private List<OzoneAcl> getAndConvertAclOnVolume(String value,
+                                                  String permission)
+      throws OS3Exception {
+    return parseAndConvertAcl(value, permission, false);
+  }
+
+  /**
+   * Parse ACL string and convert to Ozone ACLs.
+   *
+   * This is a common method extracted from getAndConvertAclOnBucket and
+   * getAndConvertAclOnVolume to reduce code duplication.
+   *
+   * @param value the ACL header value (e.g., "id=\"user1\",id=\"user2\"")
+   * @param permission the S3 permission type (READ, WRITE, etc.)
+   * @param isBucket true for bucket ACL, false for volume ACL
+   * @return list of OzoneAcl objects
+   * @throws OS3Exception if parsing fails or grantee type is not supported
+   */
+  private List<OzoneAcl> parseAndConvertAcl(String value, String permission,
+                                            boolean isBucket)
+      throws OS3Exception {
+    List<OzoneAcl> ozoneAclList = new ArrayList<>();
+    if (StringUtils.isEmpty(value)) {
+      return ozoneAclList;
+    }
+
+    String[] subValues = value.split(",");
+    for (String acl : subValues) {
+      String[] part = acl.split("=");
+      if (part.length != 2) {
+        throw newError(S3ErrorTable.INVALID_ARGUMENT, acl);
+      }
+
+      S3Acl.ACLIdentityType type =
+          S3Acl.ACLIdentityType.getTypeFromHeaderType(part[0]);
+      if (type == null || !type.isSupported()) {
+        LOG.warn("S3 grantee {} is null or not supported", part[0]);
+        throw newError(NOT_IMPLEMENTED, part[0]);
+      }
+
+      String userId = part[1];
+
+      if (isBucket) {
+        // Build ACL on Bucket
+        EnumSet<IAccessAuthorizer.ACLType> aclsOnBucket =
+            S3Acl.getOzoneAclOnBucketFromS3Permission(permission);
+        ozoneAclList.add(OzoneAcl.of(
+            IAccessAuthorizer.ACLIdentityType.USER,
+            userId,
+            OzoneAcl.AclScope.DEFAULT,
+            aclsOnBucket
+        ));

Review Comment:
   nit: please `import static` for `USER` and `DEFAULT`, then this can be written in a single line.
   
   (Also two other similar statements below.)



##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationHandler.java:
##########
@@ -0,0 +1,48 @@
+/*
+ * 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.s3.endpoint;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+
+/**
+ * Interface for handling bucket operations using chain of responsibility pattern.
+ * Each implementation handles a specific S3 bucket subresource operation
+ * (e.g., ?acl, ?lifecycle, ?notification).
+ *
+ * Implementations should extend EndpointBase to inherit all required functionality
+ * (configuration, headers, request context, audit logging, metrics, etc.).
+ */
+public interface BucketOperationHandler {
+
+  /**
+   * Handle the bucket PUT operation if this handler is responsible for it.
+   * The handler inspects the request (query parameters, headers, etc.) to determine
+   * if it should handle the request.
+   *
+   * @param bucketName the name of the bucket
+   * @param body the request body stream
+   * @return Response if this handler handles the request, null otherwise
+   * @throws IOException if an I/O error occurs
+   * @throws OS3Exception if an S3-specific error occurs
+   */
+  Response handlePutRequest(String bucketName, InputStream body)
+      throws IOException, OS3Exception;

Review Comment:
   Some handlers may not implement `PUT`, so let's make this a `default` method that `return null`.



##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/AclHandler.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * 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.s3.endpoint;
+
+import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.newError;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import javax.annotation.PostConstruct;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.ozone.OzoneAcl;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
+import org.apache.hadoop.ozone.om.helpers.OzoneAclUtil;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.util.Time;
+import org.apache.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler for bucket ACL operations (?acl query parameter).
+ * Implements PUT operations for bucket Access Control Lists.
+ *
+ * This handler extends EndpointBase to inherit all required functionality
+ * (configuration, headers, request context, audit logging, metrics, etc.).
+ */
+public class AclHandler extends EndpointBase implements BucketOperationHandler {

Review Comment:
   Please rename to `BucketAclHandler`.  Object ACL is not implemented yet, but is planned (HDDS-9631).



##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/AclHandler.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * 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.s3.endpoint;
+
+import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.newError;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import javax.annotation.PostConstruct;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.ozone.OzoneAcl;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
+import org.apache.hadoop.ozone.om.helpers.OzoneAclUtil;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.util.Time;
+import org.apache.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler for bucket ACL operations (?acl query parameter).
+ * Implements PUT operations for bucket Access Control Lists.
+ *
+ * This handler extends EndpointBase to inherit all required functionality
+ * (configuration, headers, request context, audit logging, metrics, etc.).
+ */
+public class AclHandler extends EndpointBase implements BucketOperationHandler {
+
+  private static final Logger LOG = LoggerFactory.getLogger(AclHandler.class);
+
+  /**
+   * Determine if this handler should handle the current request.
+   * @return true if the request has the "acl" query parameter
+   */
+  private boolean shouldHandle() {
+    return queryParams().get(QueryParams.ACL) != null;
+  }
+
+  /**
+   * Implement acl put.
+   * <p>
+   * see: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html
+   */
+  @Override
+  public Response handlePutRequest(String bucketName, InputStream body)
+      throws IOException, OS3Exception {
+
+    if (!shouldHandle()) {
+      return null;  // Not responsible for this request
+    }
+
+    long startNanos = Time.monotonicNowNanos();
+    S3GAction s3GAction = S3GAction.PUT_ACL;
+
+    String grantReads = getHeaders().getHeaderString(S3Acl.GRANT_READ);
+    String grantWrites = getHeaders().getHeaderString(S3Acl.GRANT_WRITE);
+    String grantReadACP = getHeaders().getHeaderString(S3Acl.GRANT_READ_ACP);
+    String grantWriteACP = getHeaders().getHeaderString(S3Acl.GRANT_WRITE_ACP);
+    String grantFull = getHeaders().getHeaderString(S3Acl.GRANT_FULL_CONTROL);
+
+    try {
+      OzoneBucket bucket = getBucket(bucketName);
+      S3Owner.verifyBucketOwnerCondition(getHeaders(), bucketName, bucket.getOwner());
+      OzoneVolume volume = getVolume();
+
+      List<OzoneAcl> ozoneAclListOnBucket = new ArrayList<>();
+      List<OzoneAcl> ozoneAclListOnVolume = new ArrayList<>();
+
+      if (grantReads == null && grantWrites == null && grantReadACP == null
+          && grantWriteACP == null && grantFull == null) {
+        // Handle grants in body
+        S3BucketAcl putBucketAclRequest =
+            new PutBucketAclRequestUnmarshaller().readFrom(body);
+        ozoneAclListOnBucket.addAll(
+            S3Acl.s3AclToOzoneNativeAclOnBucket(putBucketAclRequest));
+        ozoneAclListOnVolume.addAll(
+            S3Acl.s3AclToOzoneNativeAclOnVolume(putBucketAclRequest));
+      } else {
+        // Handle grants in headers
+        if (grantReads != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantReads,
+              S3Acl.ACLType.READ.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantReads,
+              S3Acl.ACLType.READ.getValue()));
+        }
+        if (grantWrites != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantWrites,
+              S3Acl.ACLType.WRITE.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantWrites,
+              S3Acl.ACLType.WRITE.getValue()));
+        }
+        if (grantReadACP != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantReadACP,
+              S3Acl.ACLType.READ_ACP.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantReadACP,
+              S3Acl.ACLType.READ_ACP.getValue()));
+        }
+        if (grantWriteACP != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantWriteACP,
+              S3Acl.ACLType.WRITE_ACP.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantWriteACP,
+              S3Acl.ACLType.WRITE_ACP.getValue()));
+        }
+        if (grantFull != null) {
+          ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantFull,
+              S3Acl.ACLType.FULL_CONTROL.getValue()));
+          ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantFull,
+              S3Acl.ACLType.FULL_CONTROL.getValue()));
+        }
+      }
+
+      // A put request will reset all previous ACLs on bucket
+      bucket.setAcl(ozoneAclListOnBucket);
+
+      // A put request will reset input user/group's permission on volume
+      List<OzoneAcl> acls = bucket.getAcls();
+      List<OzoneAcl> aclsToRemoveOnVolume = new ArrayList<>();
+      List<OzoneAcl> currentAclsOnVolume = volume.getAcls();
+
+      // Remove input user/group's permission from Volume first
+      if (!currentAclsOnVolume.isEmpty()) {
+        for (OzoneAcl acl : acls) {
+          if (acl.getAclScope() == ACCESS) {
+            aclsToRemoveOnVolume.addAll(OzoneAclUtil.filterAclList(
+                acl.getName(), acl.getType(), currentAclsOnVolume));
+          }
+        }
+        for (OzoneAcl acl : aclsToRemoveOnVolume) {
+          volume.removeAcl(acl);
+        }
+      }
+
+      // Add new permission on Volume
+      for (OzoneAcl acl : ozoneAclListOnVolume) {
+        volume.addAcl(acl);
+      }
+
+      getMetrics().updatePutAclSuccessStats(startNanos);
+      auditWriteSuccess(s3GAction);
+      return Response.status(HttpStatus.SC_OK).build();
+
+    } catch (OMException exception) {
+      getMetrics().updatePutAclFailureStats(startNanos);
+      auditWriteFailure(s3GAction, exception);
+      if (exception.getResult() == ResultCodes.BUCKET_NOT_FOUND) {
+        throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, exception);
+      } else if (isAccessDenied(exception)) {
+        throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, exception);
+      }
+      throw exception;
+    } catch (OS3Exception ex) {
+      getMetrics().updatePutAclFailureStats(startNanos);
+      auditWriteFailure(s3GAction, ex);
+      throw ex;
+    }
+  }
+
+  /**
+   * Convert ACL string to Ozone ACL on bucket.
+   *
+   * Example: x-amz-grant-write: id="111122223333", id="555566667777"
+   */
+  private List<OzoneAcl> getAndConvertAclOnBucket(String value,
+                                                  String permission)

Review Comment:
   nit: Please do not format method signature like this. Whenever visibility / return type / method name / other modifiers are changed, we would have to reindent all parameters.
   
   I know it's the same as it was in `BucketEndpoint`, but we can improve formatting when moving code to a new file.
   
   (Applies to few other similar methods.)



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

Posted by "adoroszlai (via GitHub)" <gi...@apache.org>.
adoroszlai commented on code in PR #9516:
URL: https://github.com/apache/ozone/pull/9516#discussion_r2630179704


##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpointContext.java:
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.s3.endpoint;
+
+import java.io.IOException;
+import org.apache.hadoop.ozone.audit.AuditAction;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+
+/**
+ * Context object that provides access to BucketEndpoint resources.
+ * This allows handlers to access endpoint functionality without
+ * tight coupling to the BucketEndpoint class.
+ * 
+ * Since BucketEndpoint extends EndpointBase, handlers can access:
+ * - Bucket and Volume operations
+ * - Methods inherited from EndpointBase
+ */
+public class BucketEndpointContext {

Review Comment:
   I don't think we should introduce `BucketEndpointContext`.  The context duplicates "random" functionality of `BucketEndpoint`.  It would likely need to grow when extracting other `PUT` operations (tagging, etc.), `GET` operations.  We will also want to extract handlers from `ObjectEndpoint`.
   
   Handlers can be considered specialised "mini" endpoints.  Thus they should simply `extend EndpointBase` to inherit all required functionality (configuration, headers, request context, audit logging, etc.).



##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java:
##########
@@ -321,13 +330,25 @@ public Response put(@PathParam("bucket") String bucketName,
     S3GAction s3GAction = S3GAction.CREATE_BUCKET;
 
     try {
-      if (aclMarker != null) {
-        s3GAction = S3GAction.PUT_ACL;
-        Response response =  putAcl(bucketName, body);
+      // Build map of query parameters
+      Map<String, String> queryParams = new HashMap<>();
+      queryParams.put("acl", aclMarker);
+      // Future handlers: queryParams.put("lifecycle", lifecycleMarker);
+
+      // Check for subresource operations using handlers
+      String queryParam = HANDLER_FACTORY.findFirstSupportedQueryParam(queryParams);
+
+      if (queryParam != null) {
+        BucketOperationHandler handler = HANDLER_FACTORY.getHandler(queryParam);
+        // Delegate to specific handler
+        s3GAction = getActionForQueryParam(queryParam);
+        Response response = handler.handlePutRequest(
+            bucketName, body, headers, getContext(), startNanos);
         AUDIT.logWriteSuccess(
             buildAuditMessageForSuccess(s3GAction, getAuditParameters()));

Review Comment:
   I think this is too complex.
   
   1. Query params are already available in a map via `ContainerRequestContext`.
   2. Handlers should take care of audit logging and metrics.  The logic to decide value of `s3GAction` should not be in centralized in `BucketEndpoint`.
   
   It is better to let handlers inspect query params (and possibly other inputs, like headers) to decide if they should handle the request or not.
   
   Something like (in `AclHandler`):
   
   ```java
     private boolean shouldHandle() {
       return hasQueryParam(ACL);
     }
   
     public Response handlePutRequest(String bucketName, InputStream body) ... {
       if (!shouldHandle()) {
         return null;
       }
       ... handle bucket ACL put
   ```
   
   Then we can simply try all handlers until one of them does the work:
   
   ```java
   for (BucketOperationHandler handler : handlers) {
     Response response = handler.handlePutRequest(bucketName, body);
     if (response != null) {
       return response;
     }
   }
   
   ... continue handling in `BucketEndpoint`
   ```



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

Posted by "adoroszlai (via GitHub)" <gi...@apache.org>.
adoroszlai merged PR #9516:
URL: https://github.com/apache/ozone/pull/9516


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

Posted by "echonesis (via GitHub)" <gi...@apache.org>.
echonesis commented on PR #9516:
URL: https://github.com/apache/ozone/pull/9516#issuecomment-3670894147

   Thanks @adoroszlai for the review.
   I will keep up with those PRs and make this simpler.


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

Posted by "echonesis (via GitHub)" <gi...@apache.org>.
echonesis commented on PR #9516:
URL: https://github.com/apache/ozone/pull/9516#issuecomment-3717211652

   Thanks @adoroszlai for the review and merge.


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14123. Refactor BucketEndpoint#Put method [ozone]

Posted by "echonesis (via GitHub)" <gi...@apache.org>.
echonesis commented on code in PR #9516:
URL: https://github.com/apache/ozone/pull/9516#discussion_r2664968934


##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationHandler.java:
##########
@@ -0,0 +1,48 @@
+/*
+ * 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.s3.endpoint;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+
+/**
+ * Interface for handling bucket operations using chain of responsibility pattern.
+ * Each implementation handles a specific S3 bucket subresource operation
+ * (e.g., ?acl, ?lifecycle, ?notification).
+ *
+ * Implementations should extend EndpointBase to inherit all required functionality
+ * (configuration, headers, request context, audit logging, metrics, etc.).
+ */
+public interface BucketOperationHandler {
+
+  /**
+   * Handle the bucket PUT operation if this handler is responsible for it.
+   * The handler inspects the request (query parameters, headers, etc.) to determine
+   * if it should handle the request.
+   *
+   * @param bucketName the name of the bucket
+   * @param body the request body stream
+   * @return Response if this handler handles the request, null otherwise
+   * @throws IOException if an I/O error occurs
+   * @throws OS3Exception if an S3-specific error occurs
+   */
+  Response handlePutRequest(String bucketName, InputStream body)
+      throws IOException, OS3Exception;

Review Comment:
   Thanks for pointing it out!
   I will update it in the next commit.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org