Repository: apache/accumulo
Pull request #5490: Modified IteratorEnvironment to no longer throw UOE
Author: login: dlmarion
Created: 2025-04-18T21:24:10Z | Merged: 2025-04-24T17:41:33Z

Modified the IteratorEnvironment interface such that the methods no longer have default implementation that throw UnsupportedOperationException. Created the class ClientIteratorEnvironment to serve as the default implementation for the client objects that defined their own implementation and for use in the tests. Removed all test implementations of IteratorEnvironment.

Closes #4810

Linked issue #4810: RFileScanner's IteratorEnvironment throws UnsupportedOperationException for getConfig() and getServiceEnv()
**Describe the bug**
When attaching an iterator to an RFileScanner which uses either the iterator environments config or ServiceEnvironment, the iterators will throw UnsupportedOperationExceptions 

**Versions (OS, Maven, Java, and others, as appropriate):**
Accumulo 2.x

**To Reproduce**
Steps to reproduce the behavior (or a link to an example repository that reproduces the problem):
1. Create an RFileScanner
2. Attach an Iterator which in the init method calls env.getConfig() or env.getServiceEnv()

**Expected behavior**
The config for the RFileScanner should be built from any table properties passed into the builder's withTableProperties() method. These properties can also be returned for any env.getServiceEnv().getConfiguration() or env.getServiceEnv().getConfiguration(TableId) methods.
Linked issue comments: user: login: kevinrr888
created_at: 2024-08-15T16:29:26Z
body: These methods have both been deprecated (`getConfig()` since `2.0.0` and `getServiceEnv()` since `2.1.0`) and replaced with `getPluginEnv()` (since `2.1.0`). 

The equivalent to 
`env.getServiceEnv()` 
would now be 
`env.getPluginEnv()` 

and the equivalent to 
`env.getConfig()` 
would now be 
`env.getPluginEnv().getConfiguration(env.getTableId())`

Hope this helps!
author_association: MEMBER
user: login: ctubbsii
created_at: 2024-08-16T02:01:40Z
body: @kevinrr888 are you saying that using the replacement APIs fixes the issue, or are you just adding extra context to whoever works on fixing the bug?
author_association: MEMBER
user: login: FineAndDandy
created_at: 2024-08-16T11:42:26Z
body: @kevinrr888 using getPluginEnv() under the covers calls getServiceEnv(), leading to the same UnsupportedOperationException. The issue is the RFileScanner uses an IteratorEnvironment implementation which implements none of these methods.
author_association: CONTRIBUTOR
user: login: kevinrr888
created_at: 2024-08-16T14:58:10Z
body: @FineAndDandy 

Yes, you are right

I saw that `getPluginEnv()` just called `getServiceEnv()` but tested (or so I thought) the `RFileScanner` and it was working for `getPluginEnv()` and `getServiceEnv()`. I assumed the `IteratorEnvironment` used for `RFileScanner` had these methods implemented. Upon looking and testing again, I see the same problem as you.

Looking into this and potentially more issues with `IteratorEnvironment` and its implemenations...

@ctubbsii 

I thought this replacement fixed the issue, but now see that this is not the case
author_association: MEMBER
user: login: FineAndDandy
created_at: 2024-08-16T15:05:07Z
body: `OfflineIteratorEnvironment` has similar issues
author_association: CONTRIBUTOR
user: login: kevinrr888
created_at: 2024-08-19T20:17:33Z
body: @FineAndDandy - The issue with `OfflineIteratorEnvironment` should be fixed in the latest release - 2.1.3 (which was very recently released). Are you on an earlier version?
author_association: MEMBER
user: login: ddanielr
created_at: 2024-08-19T21:33:30Z
body: https://github.com/apache/accumulo/pull/4283 Related code changes are here. 
author_association: CONTRIBUTOR

Diff:
diff --git a/core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java b/core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
index 217a3bb31cd..8a646219511 100644
--- a/core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
+++ b/core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
@@ -34,7 +34,6 @@
 import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.client.sample.SamplerConfiguration;
 import org.apache.accumulo.core.clientImpl.ClientContext;
-import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
 import org.apache.accumulo.core.clientImpl.ScannerImpl;
 import org.apache.accumulo.core.clientImpl.ScannerOptions;
 import org.apache.accumulo.core.data.ArrayByteSequence;
@@ -49,10 +48,10 @@
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.IteratorBuilder;
 import org.apache.accumulo.core.iteratorsImpl.IteratorConfigUtil;
 import org.apache.accumulo.core.security.Authorizations;
-import org.apache.accumulo.core.spi.common.ServiceEnvironment;
 import org.apache.hadoop.io.Text;
 
 /**
@@ -87,70 +86,6 @@ public class ClientSideIteratorScanner extends ScannerOptions implements Scanner
   private final Supplier<ClientContext> context;
   private final Supplier<TableId> tableId;
 
-  private class ClientSideIteratorEnvironment implements IteratorEnvironment {
-
-    private final SamplerConfiguration samplerConfig;
-    private final boolean sampleEnabled;
-
-    ClientSideIteratorEnvironment(boolean sampleEnabled, SamplerConfiguration samplerConfig) {
-      this.sampleEnabled = sampleEnabled;
-      this.samplerConfig = samplerConfig;
-    }
-
-    @Override
-    public IteratorScope getIteratorScope() {
-      return IteratorScope.scan;
-    }
-
-    @Override
-    public boolean isFullMajorCompaction() {
-      // The javadocs state this method will throw an ISE when scope is not majc
-      throw new IllegalStateException(
-          "Asked about major compaction type when scope is " + getIteratorScope());
-    }
-
-    @Override
-    public boolean isUserCompaction() {
-      return false;
-    }
-
-    @Override
-    public Authorizations getAuthorizations() {
-      return ClientSideIteratorScanner.this.getAuthorizations();
-    }
-
-    @Override
-    public IteratorEnvironment cloneWithSamplingEnabled() {
-      return new ClientSideIteratorEnvironment(true, samplerConfig);
-    }
-
-    @Override
-    public boolean isSamplingEnabled() {
-      return sampleEnabled;
-    }
-
-    @Override
-    public SamplerConfiguration getSamplerConfiguration() {
-      return samplerConfig;
-    }
-
-    @Deprecated(since = "2.1.0")
-    @Override
-    public ServiceEnvironment getServiceEnv() {
-      return new ClientServiceEnvironmentImpl(context.get());
-    }
-
-    @Override
-    public PluginEnvironment getPluginEnv() {
-      return new ClientServiceEnvironmentImpl(context.get());
-    }
-
-    @Override
-    public TableId getTableId() {
-      return tableId.get();
-    }
-  }
-
   /**
    * A class that wraps a Scanner in a SortedKeyValueIterator so that other accumulo iterators can
    * use it as a source.
@@ -295,9 +230,14 @@ public Iterator<Entry<Key,Value>> iterator() {
 
     SortedKeyValueIterator<Key,Value> skvi;
     try {
-      IteratorEnvironment iterEnv = new ClientSideIteratorEnvironment(
-          getSamplerConfiguration() != null, getIteratorSamplerConfigurationInternal());
-
+      ClientIteratorEnvironment.Builder builder = new ClientIteratorEnvironment.Builder()
+          .withClient(context.get()).withAuthorizations(getAuthorizations())
+          .withScope(IteratorScope.scan).withTableId(tableId.get())
+          .withSamplerConfiguration(getIteratorSamplerConfigurationInternal());
+      if (getSamplerConfiguration() != null) {
+        builder.withSamplingEnabled();
+      }
+      IteratorEnvironment iterEnv = builder.build();
       IteratorBuilder ib =
           IteratorBuilder.builder(tm.values()).opts(serverSideIteratorOptions).env(iterEnv).build();
 
diff --git a/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java b/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
index be49a9c68ac..38172594afb 100644
--- a/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
+++ b/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
@@ -30,8 +30,9 @@
 
 import org.apache.accumulo.core.client.IteratorSetting;
 import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.client.TableNotFoundException;
 import org.apache.accumulo.core.client.rfile.RFileScannerBuilder.InputArgs;
-import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
 import org.apache.accumulo.core.clientImpl.ScannerOptions;
 import org.apache.accumulo.core.conf.AccumuloConfiguration;
 import org.apache.accumulo.core.conf.ConfigurationCopy;
@@ -42,6 +43,7 @@
 import org.apache.accumulo.core.data.Column;
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.file.blockfile.cache.impl.BlockCacheConfiguration;
 import org.apache.accumulo.core.file.blockfile.cache.impl.BlockCacheManagerFactory;
@@ -55,6 +57,7 @@
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.IteratorBuilder;
 import org.apache.accumulo.core.iteratorsImpl.IteratorConfigUtil;
 import org.apache.accumulo.core.iteratorsImpl.system.MultiIterator;
@@ -66,6 +69,7 @@
 import org.apache.accumulo.core.spi.cache.CacheType;
 import org.apache.accumulo.core.spi.crypto.CryptoEnvironment;
 import org.apache.accumulo.core.spi.crypto.CryptoService;
+import org.apache.accumulo.core.util.ConfigurationImpl;
 import org.apache.accumulo.core.util.LocalityGroupUtil;
 import org.apache.hadoop.fs.FSDataInputStream;
 import org.apache.hadoop.io.Text;
@@ -74,8 +78,46 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Configuration conf;
+    private final Configuration tableConf;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      conf = new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+      ConfigurationCopy tableCC = new ConfigurationCopy(DefaultConfiguration.getInstance());
+      if (opts.tableConfig != null) {
+        opts.tableConfig.forEach(tableCC::set);
+      }
+      tableConf = new ConfigurationImpl(tableCC);
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+      return conf;
+    }
+
+    @Override
+    public Configuration getConfiguration(TableId tableId) {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return tableConf;
+    }
+
+  }
+
   private static final byte[] EMPTY_BYTES = new byte[0];
   private static final Range EMPTY_RANGE = new Range();
+  private static final String TABLE_NAME = "rfileScanner";
+  private static final TableId TABLE_ID = TableId.of(TABLE_NAME);
 
   private Range range;
   private BlockCacheManager blockCacheManager = null;
@@ -225,33 +267,6 @@ public void updateScanIteratorOption(String iteratorName, String key, String val
     super.updateScanIteratorOption(iteratorName, key, value);
   }
 
-  private class IterEnv implements IteratorEnvironment {
-    @Override
-    public IteratorScope getIteratorScope() {
-      return IteratorScope.scan;
-    }
-
-    @Override
-    public boolean isFullMajorCompaction() {
-      return false;
-    }
-
-    @Override
-    public Authorizations getAuthorizations() {
-      return opts.auths;
-    }
-
-    @Override
-    public boolean isSamplingEnabled() {
-      return RFileScanner.this.getSamplerConfiguration() != null;
-    }
-
-    @Override
-    public SamplerConfiguration getSamplerConfiguration() {
-      return RFileScanner.this.getSamplerConfiguration();
-    }
-  }
-
   @Override
   public Iterator<Entry<Key,Value>> iterator() {
     try {
@@ -292,15 +307,23 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientIteratorEnvironment.Builder iterEnvBuilder = new ClientIteratorEnvironment.Builder()
+          .withEnvironment(new RFileScannerEnvironmentImpl(opts)).withAuthorizations(opts.auths)
+          .withScope(IteratorScope.scan).withTableId(TABLE_ID);
+      if (getSamplerConfiguration() != null) {
+        iterEnvBuilder.withSamplerConfiguration(getSamplerConfiguration());
+        iterEnvBuilder.withSamplingEnabled();
+      }
+      IteratorEnvironment iterEnv = iterEnvBuilder.build();
       try {
         if (opts.tableConfig != null && !opts.tableConfig.isEmpty()) {
           var ibEnv = IteratorConfigUtil.loadIterConf(IteratorScope.scan, serverSideIteratorList,
               serverSideIteratorOptions, tableConf);
-          var iteratorBuilder = ibEnv.env(new IterEnv()).build();
+          var iteratorBuilder = ibEnv.env(iterEnv).build();
           iterator = IteratorConfigUtil.loadIterators(iterator, iteratorBuilder);
         } else {
           var iteratorBuilder = IteratorBuilder.builder(serverSideIteratorList)
-              .opts(serverSideIteratorOptions).env(new IterEnv()).build();
+              .opts(serverSideIteratorOptions).env(iterEnv).build();
           iterator = IteratorConfigUtil.loadIterators(iterator, iteratorBuilder);
         }
       } catch (IOException e) {
diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
index a03cc811ab8..d64e0181487 100644
--- a/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
+++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
@@ -34,7 +34,6 @@
 
 import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.AccumuloSecurityException;
-import org.apache.accumulo.core.client.PluginEnvironment;
 import org.apache.accumulo.core.client.SampleNotPresentException;
 import org.apache.accumulo.core.client.TableNotFoundException;
 import org.apache.accumulo.core.client.sample.SamplerConfiguration;
@@ -54,6 +53,7 @@
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.IteratorConfigUtil;
 import org.apache.accumulo.core.iteratorsImpl.system.MultiIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SystemIteratorUtil;
@@ -66,7 +66,6 @@
 import org.apache.accumulo.core.sample.impl.SamplerConfigurationImpl;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.security.ColumnVisibility;
-import org.apache.accumulo.core.spi.common.ServiceEnvironment;
 import org.apache.accumulo.core.util.LocalityGroupUtil;
 import org.apache.accumulo.core.volume.VolumeConfiguration;
 import org.apache.hadoop.conf.Configuration;
@@ -75,105 +74,6 @@
 
 class OfflineIterator implements Iterator<Entry<Key,Value>> {
 
-  static class OfflineIteratorEnvironment implements IteratorEnvironment {
-
-    private final Authorizations authorizations;
-    private final AccumuloConfiguration conf;
-    private final boolean useSample;
-    private final SamplerConfiguration sampleConf;
-    private final ClientContext context;
-    private final TableId tableId;
-
-    public OfflineIteratorEnvironment(ClientContext context, TableId tableId, Authorizations auths,
-        AccumuloConfiguration acuTableConf, boolean useSample, SamplerConfiguration samplerConf) {
-      this.context = context;
-      this.tableId = tableId;
-      this.authorizations = auths;
-      this.conf = acuTableConf;
-      this.useSample = useSample;
-      this.sampleConf = samplerConf;
-    }
-
-    @Deprecated(since = "2.0.0")
-    @Override
-    public AccumuloConfiguration getConfig() {
-      return conf;
-    }
-
-    @Override
-    public IteratorScope getIteratorScope() {
-      return IteratorScope.scan;
-    }
-
-    @Override
-    public boolean isFullMajorCompaction() {
-      return false;
-    }
-
-    @Override
-    public boolean isUserCompaction() {
-      return false;
-    }
-
-    private final ArrayList<SortedKeyValueIterator<Key,Value>> topLevelIterators =
-        new ArrayList<>();
-
-    @Deprecated(since = "2.0.0")
-    @Override
-    public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
-      topLevelIterators.add(iter);
-    }
-
-    @Override
-    public Authorizations getAuthorizations() {
-      return authorizations;
-    }
-
-    SortedKeyValueIterator<Key,Value> getTopLevelIterator(SortedKeyValueIterator<Key,Value> iter) {
-      if (topLevelIterators.isEmpty()) {
-        return iter;
-      }
-      ArrayList<SortedKeyValueIterator<Key,Value>> allIters = new ArrayList<>(topLevelIterators);
-      allIters.add(iter);
-      return new MultiIterator(allIters, false);
-    }
-
-    @Override
-    public boolean isSamplingEnabled() {
-      return useSample;
-    }
-
-    @Override
-    public SamplerConfiguration getSamplerConfiguration() {
-      return sampleConf;
-    }
-
-    @Override
-    public IteratorEnvironment cloneWithSamplingEnabled() {
-      if (sampleConf == null) {
-        throw new SampleNotPresentException();
-      }
-      return new OfflineIteratorEnvironment(context, tableId, authorizations, conf, true,
-          sampleConf);
-    }
-
-    @Deprecated(since = "2.1.0")
-    @Override
-    public ServiceEnvironment getServiceEnv() {
-      return new ClientServiceEnvironmentImpl(context);
-    }
-
-    @Override
-    public PluginEnvironment getPluginEnv() {
-      return new ClientServiceEnvironmentImpl(context);
-    }
-
-    @Override
-    public TableId getTableId() {
-      return tableId;
-    }
-  }
-
   private SortedKeyValueIterator<Key,Value> iter;
   private Range range;
   private KeyExtent currentExtent;
@@ -345,9 +245,13 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
 
     MultiIterator multiIter = new MultiIterator(readers, extent);
 
-    OfflineIteratorEnvironment iterEnv =
-        new OfflineIteratorEnvironment(context, tableId, authorizations, tableCC, false,
-            samplerConfImpl == null ? null : samplerConfImpl.toSamplerConfiguration());
+    ClientIteratorEnvironment.Builder iterEnvBuilder =
+        new ClientIteratorEnvironment.Builder().withAuthorizations(authorizations)
+            .withScope(IteratorScope.scan).withTableId(tableId).withClient(context);
+    if (samplerConfImpl != null) {
+      iterEnvBuilder.withSamplerConfiguration(samplerConfImpl.toSamplerConfiguration());
+    }
+    IteratorEnvironment iterEnv = iterEnvBuilder.build();
 
     byte[] defaultSecurityLabel;
     ColumnVisibility cv =
@@ -360,8 +264,7 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
     var iteratorBuilderEnv = IteratorConfigUtil.loadIterConf(IteratorScope.scan,
         options.serverSideIteratorList, options.serverSideIteratorOptions, tableCC);
     var iteratorBuilder = iteratorBuilderEnv.env(iterEnv).build();
-    return iterEnv
-        .getTopLevelIterator(IteratorConfigUtil.loadIterators(visFilter, iteratorBuilder));
+    return IteratorConfigUtil.loadIterators(visFilter, iteratorBuilder);
   }
 
   @Override
diff --git a/core/src/main/java/org/apache/accumulo/core/iterators/IteratorEnvironment.java b/core/src/main/java/org/apache/accumulo/core/iterators/IteratorEnvironment.java
index 372a0e49a30..1646a196388 100644
--- a/core/src/main/java/org/apache/accumulo/core/iterators/IteratorEnvironment.java
+++ b/core/src/main/java/org/apache/accumulo/core/iterators/IteratorEnvironment.java
@@ -24,6 +24,7 @@
 import org.apache.accumulo.core.client.SampleNotPresentException;
 import org.apache.accumulo.core.client.sample.SamplerConfiguration;
 import org.apache.accumulo.core.conf.AccumuloConfiguration;
+import org.apache.accumulo.core.conf.ConfigurationCopy;
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.data.Value;
@@ -37,10 +38,7 @@ public interface IteratorEnvironment {
    * @deprecated since 2.0.0. This is a legacy method used for internal backwards compatibility.
    */
   @Deprecated(since = "2.0.0")
-  default SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
-      throws IOException {
-    throw new UnsupportedOperationException();
-  }
+  SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName) throws IOException;
 
   /**
    * @deprecated since 2.0.0. This method was using an unstable non public type. Use
@@ -48,40 +46,32 @@ default SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileNam
    */
   @Deprecated(since = "2.0.0")
   default AccumuloConfiguration getConfig() {
-    throw new UnsupportedOperationException();
+    return new ConfigurationCopy(getPluginEnv().getConfiguration());
   }
 
   /**
    * Return the executed scope of the Iterator. Value will be one of the following:
    * {@link IteratorScope#scan}, {@link IteratorScope#minc}, {@link IteratorScope#majc}
    */
-  default IteratorScope getIteratorScope() {
-    throw new UnsupportedOperationException();
-  }
+  IteratorScope getIteratorScope();
 
   /**
    * Return true if the compaction is a full major compaction. Will throw IllegalStateException if
    * {@link #getIteratorScope()} != {@link IteratorScope#majc}.
    */
-  default boolean isFullMajorCompaction() {
-    throw new UnsupportedOperationException();
-  }
+  boolean isFullMajorCompaction();
 
   /**
    * @deprecated since 2.0.0. This was an experimental feature and was never tested or documented.
    */
   @Deprecated(since = "2.0.0")
-  default void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
-    throw new UnsupportedOperationException();
-  }
+  void registerSideChannel(SortedKeyValueIterator<Key,Value> iter);
 
   /**
    * Return the Scan Authorizations used in this Iterator. Will throw UnsupportedOperationException
    * if {@link #getIteratorScope()} != {@link IteratorScope#scan}.
    */
-  default Authorizations getAuthorizations() {
-    throw new UnsupportedOperationException();
-  }
+  Authorizations getAuthorizations();
 
   /**
    * Returns a new iterator environment object that can be used to create deep copies over sample
@@ -113,9 +103,7 @@ default Authorizations getAuthorizations() {
    * @throws SampleNotPresentException when sampling is not configured for table.
    * @since 1.8.0
    */
-  default IteratorEnvironment cloneWithSamplingEnabled() {
-    throw new UnsupportedOperationException();
-  }
+  IteratorEnvironment cloneWithSamplingEnabled();
 
   /**
    * There are at least two conditions under which sampling will be enabled for an environment. One
@@ -126,27 +114,21 @@ default IteratorEnvironment cloneWithSamplingEnabled() {
    * @return true if sampling is enabled for this environment.
    * @since 1.8.0
    */
-  default boolean isSamplingEnabled() {
-    throw new UnsupportedOperationException();
-  }
+  boolean isSamplingEnabled();
 
   /**
    *
    * @return sampling configuration is sampling is enabled for environment, otherwise returns null.
    * @since 1.8.0
    */
-  default SamplerConfiguration getSamplerConfiguration() {
-    throw new UnsupportedOperationException();
-  }
+  SamplerConfiguration getSamplerConfiguration();
 
   /**
    * True if compaction was user initiated.
    *
    * @since 2.0.0
    */
-  default boolean isUserCompaction() {
-    throw new UnsupportedOperationException();
-  }
+  boolean isUserCompaction();
 
   /**
    * Returns an object containing information about the server where this iterator was run. To
@@ -161,9 +143,7 @@ default boolean isUserCompaction() {
    *             {@link #getPluginEnv()} instead because it has better stability guarantees.
    */
   @Deprecated(since = "2.1.0")
-  default ServiceEnvironment getServiceEnv() {
-    throw new UnsupportedOperationException();
-  }
+  ServiceEnvironment getServiceEnv();
 
   /**
    * Returns an object containing information about the server where this iterator was run. To
@@ -175,16 +155,12 @@ default ServiceEnvironment getServiceEnv() {
    *
    * @since 2.1.0
    */
-  default PluginEnvironment getPluginEnv() {
-    return getServiceEnv();
-  }
+  PluginEnvironment getPluginEnv();
 
   /**
    * Return the table Id associated with this iterator.
    *
    * @since 2.0.0
    */
-  default TableId getTableId() {
-    throw new UnsupportedOperationException();
-  }
+  TableId getTableId();
 }
diff --git a/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/ClientIteratorEnvironment.java b/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/ClientIteratorEnvironment.java
new file mode 100644
index 00000000000..41a514f19aa
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/ClientIteratorEnvironment.java
@@ -0,0 +1,227 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iteratorsImpl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      checkState(this.scope.isEmpty(), "Scope has already been set");
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      checkState(this.auths.isEmpty(), "Authorizations have already been set");
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      checkState(this.tableId.isEmpty(), "TableId has already been set");
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      checkState(this.samplerConfig.isEmpty(), "SamplerConfiguration has already been set");
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      checkState(this.env.isEmpty(), "ClientServiceEnvironmentImpl has already been set");
+      this.env = Optional.of(env);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      checkState(this.env.isEmpty(), "ClientServiceEnvironmentImpl has already been set");
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not scan");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (samplerConfig.isEmpty()) {
+      throw new SampleNotPresentException();
+    }
+    return new ClientIteratorEnvironment(this);
+  }
+
+  @Override
+  public boolean isSamplingEnabled() {
+    return this.samplingEnabled;
+  }
+
+  @Override
+  public SamplerConfiguration getSamplerConfiguration() {
+    if (!isSamplingEnabled()) {
+      return null;
+    }
+    return samplerConfig.orElseThrow();
+  }
+
+  @Override
+  public boolean isUserCompaction() {
+    if (getIteratorScope() == IteratorScope.scan) {
+      throw new IllegalStateException(
+          "scan iterator scope is incompatible with a possible user compaction");
+    }
+    return this.isUserCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.1.0")
+  public ServiceEnvironment getServiceEnv() {
+    return env.orElseThrow();
+  }
+
+  @Override
+  public PluginEnvironment getPluginEnv() {
+    return env.orElseThrow();
+  }
+
+  @Override
+  public TableId getTableId() {
+    return this.tableId.orElseThrow();
+  }
+
+}
diff --git a/core/src/test/java/org/apache/accumulo/core/file/rfile/RFileTest.java b/core/src/test/java/org/apache/accumulo/core/file/rfile/RFileTest.java
index 4b0c533dd1d..0bb58e6bb55 100644
--- a/core/src/test/java/org/apache/accumulo/core/file/rfile/RFileTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/file/rfile/RFileTest.java
@@ -49,7 +49,6 @@
 
 import org.apache.accumulo.core.client.sample.RowSampler;
 import org.apache.accumulo.core.client.sample.Sampler;
-import org.apache.accumulo.core.client.sample.SamplerConfiguration;
 import org.apache.accumulo.core.conf.AccumuloConfiguration;
 import org.apache.accumulo.core.conf.ConfigurationCopy;
 import org.apache.accumulo.core.conf.DefaultConfiguration;
@@ -74,6 +73,7 @@
 import org.apache.accumulo.core.file.rfile.bcfile.BCFile;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.ColumnFamilySkippingIterator;
 import org.apache.accumulo.core.metadata.MetadataTable;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
@@ -107,26 +107,6 @@
 public class RFileTest {
 
   private static final SecureRandom random = new SecureRandom();
-
-  public static class SampleIE implements IteratorEnvironment {
-
-    private SamplerConfiguration samplerConfig;
-
-    SampleIE(SamplerConfiguration config) {
-      this.samplerConfig = config;
-    }
-
-    @Override
-    public boolean isSamplingEnabled() {
-      return samplerConfig != null;
-    }
-
-    @Override
-    public SamplerConfiguration getSamplerConfiguration() {
-      return samplerConfig;
-    }
-  }
-
   private static final Collection<ByteSequence> EMPTY_COL_FAMS = new ArrayList<>();
   private static final Configuration hadoopConf = new Configuration();
 
@@ -2068,15 +2048,16 @@ public void testSample() throws IOException {
 
         trf.openReader();
 
-        FileSKVIterator sample =
-            trf.reader.getSample(SamplerConfigurationImpl.newSamplerConfig(sampleConf));
+        SamplerConfigurationImpl sc = SamplerConfigurationImpl.newSamplerConfig(sampleConf);
+
+        FileSKVIterator sample = trf.reader.getSample(sc);
 
         checkSample(sample, sampleData);
 
         assertEquals(expectedDataHash, hash(trf.reader));
 
-        SampleIE ie = new SampleIE(
-            SamplerConfigurationImpl.newSamplerConfig(sampleConf).toSamplerConfiguration());
+        IteratorEnvironment ie = new ClientIteratorEnvironment.Builder()
+            .withSamplerConfiguration(sc.toSamplerConfiguration()).withSamplingEnabled().build();
 
         for (int i = 0; i < 3; i++) {
           // test opening and closing deep copies a few times.
@@ -2086,8 +2067,10 @@ public void testSample() throws IOException {
           SortedKeyValueIterator<Key,Value> sampleDC1 = sample.deepCopy(ie);
           SortedKeyValueIterator<Key,Value> sampleDC2 = sample.deepCopy(ie);
           SortedKeyValueIterator<Key,Value> sampleDC3 = trf.reader.deepCopy(ie);
-          SortedKeyValueIterator<Key,Value> allDC1 = sampleDC1.deepCopy(new SampleIE(null));
-          SortedKeyValueIterator<Key,Value> allDC2 = sample.deepCopy(new SampleIE(null));
+          SortedKeyValueIterator<Key,Value> allDC1 =
+              sampleDC1.deepCopy(ClientIteratorEnvironment.DEFAULT);
+          SortedKeyValueIterator<Key,Value> allDC2 =
+              sample.deepCopy(ClientIteratorEnvironment.DEFAULT);
 
           assertEquals(expectedDataHash, hash(allDC1));
           assertEquals(expectedDataHash, hash(allDC2));
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/DefaultIteratorEnvironment.java b/core/src/test/java/org/apache/accumulo/core/iterators/DefaultIteratorEnvironment.java
deleted file mode 100644
index 802dfe849cc..00000000000
--- a/core/src/test/java/org/apache/accumulo/core/iterators/DefaultIteratorEnvironment.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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
- *
- *   https://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.accumulo.core.iterators;
-
-import java.io.IOException;
-
-import org.apache.accumulo.core.conf.AccumuloConfiguration;
-import org.apache.accumulo.core.conf.DefaultConfiguration;
-import org.apache.accumulo.core.data.Key;
-import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.FileSystem;
-
-public class DefaultIteratorEnvironment implements IteratorEnvironment {
-
-  AccumuloConfiguration conf;
-  Configuration hadoopConf = new Configuration();
-
-  public DefaultIteratorEnvironment(AccumuloConfiguration conf) {
-    this.conf = conf;
-  }
-
-  public DefaultIteratorEnvironment() {
-    this.conf = DefaultConfiguration.getInstance();
-  }
-
-  @Deprecated(since = "2.0.0")
-  @Override
-  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
-      throws IOException {
-    FileSystem fs = FileSystem.get(hadoopConf);
-    return new MapFileIterator(fs, mapFileName, hadoopConf);
-  }
-
-  @Override
-  public boolean isSamplingEnabled() {
-    return false;
-  }
-}
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/FirstEntryInRowIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/FirstEntryInRowIteratorTest.java
index 5a7f6dbcc07..06e8311677a 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/FirstEntryInRowIteratorTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/FirstEntryInRowIteratorTest.java
@@ -30,6 +30,7 @@
 import org.apache.accumulo.core.data.PartialKey;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.CountingIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.junit.jupiter.api.Test;
@@ -41,9 +42,8 @@ private static long process(TreeMap<Key,Value> sourceMap, TreeMap<Key,Value> res
     SortedMapIterator source = new SortedMapIterator(sourceMap);
     CountingIterator counter = new CountingIterator(source);
     FirstEntryInRowIterator feiri = new FirstEntryInRowIterator();
-    IteratorEnvironment env = new DefaultIteratorEnvironment();
 
-    feiri.init(counter, iteratorSetting.getOptions(), env);
+    feiri.init(counter, iteratorSetting.getOptions(), ClientIteratorEnvironment.DEFAULT);
 
     feiri.seek(range, Set.of(), false);
     while (feiri.hasTop()) {
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/SortedMapIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/SortedMapIteratorTest.java
index c35cf8f1c6a..3dab1bf667e 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/SortedMapIteratorTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/SortedMapIteratorTest.java
@@ -25,6 +25,7 @@
 import org.apache.accumulo.core.client.SampleNotPresentException;
 import org.apache.accumulo.core.client.sample.RowSampler;
 import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.junit.jupiter.api.Test;
 
@@ -33,16 +34,9 @@ public class SortedMapIteratorTest {
   @Test
   public void testSampleNotPresent() {
     SortedMapIterator smi = new SortedMapIterator(new TreeMap<>());
-    assertThrows(SampleNotPresentException.class, () -> smi.deepCopy(new IteratorEnvironment() {
-      @Override
-      public boolean isSamplingEnabled() {
-        return true;
-      }
-
-      @Override
-      public SamplerConfiguration getSamplerConfiguration() {
-        return new SamplerConfiguration(RowSampler.class.getName());
-      }
-    }));
+    assertThrows(SampleNotPresentException.class,
+        () -> smi.deepCopy(new ClientIteratorEnvironment.Builder()
+            .withSamplerConfiguration(new SamplerConfiguration(RowSampler.class.getName()))
+            .withSamplingEnabled().build()));
   }
 }
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/ColumnSliceFilterTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/ColumnSliceFilterTest.java
index 1fe7f3ac524..0a0716006e9 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/ColumnSliceFilterTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/ColumnSliceFilterTest.java
@@ -34,8 +34,8 @@
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.BeforeEach;
@@ -72,7 +72,7 @@ private static Key newKey(String row, String cf, String cq) {
   @BeforeEach
   public void setUp() {
     columnSliceFilter.describeOptions();
-    iteratorEnvironment = new DefaultIteratorEnvironment();
+    iteratorEnvironment = ClientIteratorEnvironment.DEFAULT;
     is = new IteratorSetting(1, ColumnSliceFilter.class);
   }
 
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/CombinerTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/CombinerTest.java
index df757966903..04c64acf6dc 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/CombinerTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/CombinerTest.java
@@ -40,7 +40,6 @@
 import org.apache.accumulo.core.iterators.Combiner;
 import org.apache.accumulo.core.iterators.Combiner.ValueIterator;
 import org.apache.accumulo.core.iterators.CombinerTestUtil;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.LongCombiner;
@@ -50,6 +49,7 @@
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
 import org.apache.accumulo.core.iterators.TypedValueCombiner;
 import org.apache.accumulo.core.iterators.ValueFormatException;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.MultiIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.hadoop.io.Text;
@@ -59,29 +59,8 @@ public class CombinerTest {
 
   private static final Collection<ByteSequence> EMPTY_COL_FAMS = new ArrayList<>();
 
-  static class CombinerIteratorEnvironment extends DefaultIteratorEnvironment {
-
-    private IteratorScope scope;
-    private boolean isFullMajc;
-
-    CombinerIteratorEnvironment(IteratorScope scope, boolean isFullMajc) {
-      this.scope = scope;
-      this.isFullMajc = isFullMajc;
-    }
-
-    @Override
-    public IteratorScope getIteratorScope() {
-      return scope;
-    }
-
-    @Override
-    public boolean isFullMajorCompaction() {
-      return isFullMajc;
-    }
-  }
-
   static final IteratorEnvironment SCAN_IE =
-      new CombinerIteratorEnvironment(IteratorScope.scan, false);
+      new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build();
 
   static Key newKey(int row, int colf, int colq, long ts, boolean deleted) {
     Key k = newKey(row, colf, colq, ts);
@@ -884,8 +863,10 @@ public void testDeleteHandling() throws Exception {
 
     TreeMap<Key,Value> input = new TreeMap<>();
 
-    IteratorEnvironment paritalMajcIe = new CombinerIteratorEnvironment(IteratorScope.majc, false);
-    IteratorEnvironment fullMajcIe = new CombinerIteratorEnvironment(IteratorScope.majc, true);
+    IteratorEnvironment paritalMajcIe =
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.majc).build();
+    IteratorEnvironment fullMajcIe = new ClientIteratorEnvironment.Builder()
+        .withScope(IteratorScope.majc).isFullMajorCompaction().build();
 
     // keys that aggregate
     newKeyValue(input, 1, 1, 1, 1, false, 4L, encoder);
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/FilterTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/FilterTest.java
index 996b3df97a5..c1dd3466769 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/FilterTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/FilterTest.java
@@ -40,9 +40,9 @@
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
 import org.apache.accumulo.core.iterators.Filter;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.ColumnQualifierFilter;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.VisibilityFilter;
@@ -230,19 +230,19 @@ public void test2a() throws IOException {
 
     ColumnAgeOffFilter a = new ColumnAgeOffFilter();
     assertTrue(a.validateOptions(is.getOptions()));
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
     assertEquals(902, size(a));
 
     ColumnAgeOffFilter.addTTL(is, new IteratorSetting.Column("a", "b"), 101L);
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
     assertEquals(102, size(a));
 
     ColumnAgeOffFilter.removeTTL(is, new IteratorSetting.Column("a", "b"));
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a = (ColumnAgeOffFilter) a.deepCopy(null);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
@@ -271,19 +271,19 @@ public void test2aNegate() throws IOException {
 
     ColumnAgeOffFilter a = new ColumnAgeOffFilter();
     assertTrue(a.validateOptions(is.getOptions()));
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
     assertEquals(98, size(a));
 
     ColumnAgeOffFilter.addTTL(is, new IteratorSetting.Column("a", "b"), 101L);
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
     assertEquals(898, size(a));
 
     ColumnAgeOffFilter.removeTTL(is, new IteratorSetting.Column("a", "b"));
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a = (ColumnAgeOffFilter) a.deepCopy(null);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
@@ -312,19 +312,19 @@ public void test2b() throws IOException {
 
     ColumnAgeOffFilter a = new ColumnAgeOffFilter();
     assertTrue(a.validateOptions(is.getOptions()));
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
     assertEquals(902, size(a));
 
     ColumnAgeOffFilter.addTTL(is, new IteratorSetting.Column("negate", "b"), 101L);
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
     assertEquals(102, size(a));
 
     ColumnAgeOffFilter.removeTTL(is, new IteratorSetting.Column("negate", "b"));
-    a.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    a.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     a = (ColumnAgeOffFilter) a.deepCopy(null);
     a.overrideCurrentTime(ts);
     a.seek(new Range(), EMPTY_COL_FAMS, false);
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/IndexedDocIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/IndexedDocIteratorTest.java
index fdb34e1fa36..b9ee9c6a3a9 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/IndexedDocIteratorTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/IndexedDocIteratorTest.java
@@ -38,9 +38,9 @@
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.file.rfile.RFileTest;
 import org.apache.accumulo.core.file.rfile.RFileTest.TestRFile;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.MultiIterator;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.Test;
@@ -51,7 +51,7 @@ public class IndexedDocIteratorTest {
   private static final Collection<ByteSequence> EMPTY_COL_FAMS = new ArrayList<>();
   private static final byte[] nullByte = {0};
 
-  private static IteratorEnvironment env = new DefaultIteratorEnvironment();
+  private static IteratorEnvironment env = ClientIteratorEnvironment.DEFAULT;
 
   Text[] columnFamilies;
   Text[] otherColumnFamilies;
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/IntersectingIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/IntersectingIteratorTest.java
index 2c3710d9579..7106db27706 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/IntersectingIteratorTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/IntersectingIteratorTest.java
@@ -33,9 +33,9 @@
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.MultiIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.hadoop.io.Text;
@@ -45,7 +45,7 @@ public class IntersectingIteratorTest {
 
   private static final SecureRandom random = new SecureRandom();
   private static final Collection<ByteSequence> EMPTY_COL_FAMS = new ArrayList<>();
-  private static IteratorEnvironment env = new DefaultIteratorEnvironment();
+  private static IteratorEnvironment env = ClientIteratorEnvironment.DEFAULT;
 
   HashSet<Text> docs = new HashSet<>();
   Text[] columnFamilies;
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/LargeRowFilterTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/LargeRowFilterTest.java
index b0a83759348..fd6c7c6b9b5 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/LargeRowFilterTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/LargeRowFilterTest.java
@@ -34,6 +34,7 @@
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.ColumnFamilySkippingIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.junit.jupiter.api.Test;
@@ -67,7 +68,7 @@ private LargeRowFilter setupIterator(TreeMap<Key,Value> testData, int maxColumns
     IteratorSetting is = new IteratorSetting(1, LargeRowFilter.class);
     LargeRowFilter.setMaxColumns(is, maxColumns);
     lrfi.init(new ColumnFamilySkippingIterator(smi), is.getOptions(),
-        new RowDeletingIteratorTest.TestIE(scope, false));
+        new ClientIteratorEnvironment.Builder().withScope(scope).build());
     return lrfi;
   }
 
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/RegExFilterTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/RegExFilterTest.java
index 3a558374b4a..8bcf1dac763 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/RegExFilterTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/RegExFilterTest.java
@@ -33,7 +33,7 @@
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.Test;
@@ -67,7 +67,7 @@ public void test1() throws IOException {
     RegExFilter.setRegexs(is, ".*2", null, null, null, false);
 
     assertTrue(rei.validateOptions(is.getOptions()));
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -82,7 +82,7 @@ public void test1() throws IOException {
     RegExFilter.setRegexs(is, null, null, null, "amst", false, true); // Should only match hamster
 
     rei.validateOptions(is.getOptions());
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -95,7 +95,7 @@ public void test1() throws IOException {
 
     RegExFilter.setRegexs(is, null, "ya.*", null, null, false);
     assertTrue(rei.validateOptions(is.getOptions()));
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -108,7 +108,7 @@ public void test1() throws IOException {
 
     RegExFilter.setRegexs(is, null, null, ".*01", null, false);
     assertTrue(rei.validateOptions(is.getOptions()));
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -121,7 +121,7 @@ public void test1() throws IOException {
 
     RegExFilter.setRegexs(is, null, null, null, ".*at", false);
     assertTrue(rei.validateOptions(is.getOptions()));
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -133,7 +133,7 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, null, null, null, ".*ap", false);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertFalse(rei.hasTop());
@@ -142,7 +142,7 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, null, "ya.*", null, ".*at", false);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -154,7 +154,7 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, null, "ya.*", null, ".*ap", false);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertFalse(rei.hasTop());
@@ -163,7 +163,7 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, "boo1", null, null, null, false);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -177,7 +177,7 @@ public void test1() throws IOException {
     // -----------------------------------------------------
     is.clearOptions();
 
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -195,7 +195,7 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, "hamster", null, "hamster", "hamster", true);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -207,7 +207,7 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, null, "ya.*", "hamster", null, true);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
@@ -218,9 +218,9 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, null, "ya.*", "hamster", null, true);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
-    rei.deepCopy(new DefaultIteratorEnvironment());
+    rei.deepCopy(ClientIteratorEnvironment.DEFAULT);
 
     // -----------------------------------------------------
     String multiByteText = new String("\u6d67\u6F68\u7067");
@@ -234,7 +234,7 @@ public void test1() throws IOException {
     is.clearOptions();
 
     RegExFilter.setRegexs(is, null, null, null, multiByteRegex, true);
-    rei.init(new SortedMapIterator(tm), is.getOptions(), new DefaultIteratorEnvironment());
+    rei.init(new SortedMapIterator(tm), is.getOptions(), ClientIteratorEnvironment.DEFAULT);
     rei.seek(new Range(), EMPTY_COL_FAMS, false);
 
     assertTrue(rei.hasTop());
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/RowDeletingIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/RowDeletingIteratorTest.java
index 39f0547cff8..0a8ee00998a 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/RowDeletingIteratorTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/RowDeletingIteratorTest.java
@@ -32,8 +32,8 @@
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.ColumnFamilySkippingIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.hadoop.io.Text;
@@ -41,27 +41,6 @@
 
 public class RowDeletingIteratorTest {
 
-  public static class TestIE implements IteratorEnvironment {
-
-    private IteratorScope scope;
-    private boolean fmc;
-
-    public TestIE(IteratorScope scope, boolean fmc) {
-      this.scope = scope;
-      this.fmc = fmc;
-    }
-
-    @Override
-    public IteratorScope getIteratorScope() {
-      return scope;
-    }
-
-    @Override
-    public boolean isFullMajorCompaction() {
-      return fmc;
-    }
-  }
-
   Key newKey(String row, String cf, String cq, long time) {
     return new Key(new Text(row), new Text(cf), new Text(cq), time);
   }
@@ -91,7 +70,8 @@ public void test1() throws Exception {
     put(tm1, "r2", "cf1", "cq1", 5, "v1");
 
     RowDeletingIterator rdi = new RowDeletingIterator();
-    rdi.init(new SortedMapIterator(tm1), null, new TestIE(IteratorScope.scan, false));
+    rdi.init(new SortedMapIterator(tm1), null,
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build());
 
     rdi.seek(new Range(), new ArrayList<>(), false);
     testAssertions(rdi, "r2", "cf1", "cq1", 5, "v1");
@@ -133,7 +113,8 @@ public void test2() throws Exception {
     put(tm1, "r2", "cf1", "cq1", 5, "v1");
 
     RowDeletingIterator rdi = new RowDeletingIterator();
-    rdi.init(new SortedMapIterator(tm1), null, new TestIE(IteratorScope.scan, false));
+    rdi.init(new SortedMapIterator(tm1), null,
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build());
 
     rdi.seek(new Range(), new ArrayList<>(), false);
     testAssertions(rdi, "r1", "cf1", "cq3", 15, "v1");
@@ -175,7 +156,7 @@ public void test3() throws Exception {
 
     RowDeletingIterator rdi = new RowDeletingIterator();
     rdi.init(new ColumnFamilySkippingIterator(new SortedMapIterator(tm1)), null,
-        new TestIE(IteratorScope.scan, false));
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build());
 
     HashSet<ByteSequence> cols = new HashSet<>();
     cols.add(new ArrayByteSequence("cf1".getBytes(UTF_8)));
@@ -206,7 +187,8 @@ public void test4() throws Exception {
     put(tm1, "r2", "cf1", "cq1", 5, "v1");
 
     RowDeletingIterator rdi = new RowDeletingIterator();
-    rdi.init(new SortedMapIterator(tm1), null, new TestIE(IteratorScope.minc, false));
+    rdi.init(new SortedMapIterator(tm1), null,
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.minc).build());
 
     rdi.seek(new Range(), new ArrayList<>(), false);
     testAssertions(rdi, "r1", "", "", 10, RowDeletingIterator.DELETE_ROW_VALUE.toString());
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/RowEncodingIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/RowEncodingIteratorTest.java
index 91908a7764f..63da92a45c3 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/RowEncodingIteratorTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/RowEncodingIteratorTest.java
@@ -38,26 +38,14 @@
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.IteratorEnvironment;
-import org.apache.accumulo.core.iterators.IteratorUtil;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.Test;
 
 public class RowEncodingIteratorTest {
 
-  private static final class DummyIteratorEnv implements IteratorEnvironment {
-    @Override
-    public IteratorUtil.IteratorScope getIteratorScope() {
-      return IteratorUtil.IteratorScope.scan;
-    }
-
-    @Override
-    public boolean isFullMajorCompaction() {
-      return false;
-    }
-  }
-
   private static final class RowEncodingIteratorImpl extends RowEncodingIterator {
 
     public static SortedMap<Key,Value> decodeRow(Value rowValue) throws IOException {
@@ -139,7 +127,8 @@ public void testEncodeAll() throws IOException {
     RowEncodingIteratorImpl iter = new RowEncodingIteratorImpl();
     Map<String,String> bigBufferOpts = new HashMap<>();
     bigBufferOpts.put(RowEncodingIterator.MAX_BUFFER_SIZE_OPT, "3K");
-    iter.init(src, bigBufferOpts, new DummyIteratorEnv());
+    iter.init(src, bigBufferOpts,
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build());
     iter.seek(range, new ArrayList<>(), false);
 
     assertTrue(iter.hasTop());
@@ -173,7 +162,8 @@ public void testEncodeSome() throws IOException {
     RowEncodingIteratorImpl iter = new RowEncodingIteratorImpl();
     Map<String,String> bigBufferOpts = new HashMap<>();
     bigBufferOpts.put(RowEncodingIterator.MAX_BUFFER_SIZE_OPT, "1K");
-    iter.init(src, bigBufferOpts, new DummyIteratorEnv());
+    iter.init(src, bigBufferOpts,
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build());
     assertThrows(IllegalArgumentException.class, () -> iter.seek(range, new ArrayList<>(), false));
     // IllegalArgumentException should be thrown as we can't fit the whole row into its buffer
   }
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/RowFilterTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/RowFilterTest.java
index a18583efb43..4c106559ecd 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/RowFilterTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/RowFilterTest.java
@@ -39,9 +39,9 @@
 import org.apache.accumulo.core.data.Mutation;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.ColumnFamilySkippingIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.hadoop.io.Text;
@@ -205,7 +205,7 @@ public void test1() throws Exception {
         new ColumnFamilySkippingIterator(new SortedMapIterator(createKeyValues()));
 
     RowFilter filter = new SummingRowFilter();
-    filter.init(source, Collections.emptyMap(), new DefaultIteratorEnvironment());
+    filter.init(source, Collections.emptyMap(), ClientIteratorEnvironment.DEFAULT);
 
     filter.seek(new Range(), Collections.emptySet(), false);
 
@@ -235,10 +235,10 @@ public void testChainedRowFilters() throws Exception {
     SortedMapIterator source = new SortedMapIterator(createKeyValues());
 
     RowFilter filter0 = new TrueFilter();
-    filter0.init(source, Collections.emptyMap(), new DefaultIteratorEnvironment());
+    filter0.init(source, Collections.emptyMap(), ClientIteratorEnvironment.DEFAULT);
 
     RowFilter filter = new TrueFilter();
-    filter.init(filter0, Collections.emptyMap(), new DefaultIteratorEnvironment());
+    filter.init(filter0, Collections.emptyMap(), ClientIteratorEnvironment.DEFAULT);
 
     filter.seek(new Range(), Collections.emptySet(), false);
 
@@ -251,10 +251,10 @@ public void testFilterConjunction() throws Exception {
     SortedMapIterator source = new SortedMapIterator(createKeyValues());
 
     RowFilter filter0 = new RowZeroOrOneFilter();
-    filter0.init(source, Collections.emptyMap(), new DefaultIteratorEnvironment());
+    filter0.init(source, Collections.emptyMap(), ClientIteratorEnvironment.DEFAULT);
 
     RowFilter filter = new RowOneOrTwoFilter();
-    filter.init(filter0, Collections.emptyMap(), new DefaultIteratorEnvironment());
+    filter.init(filter0, Collections.emptyMap(), ClientIteratorEnvironment.DEFAULT);
 
     filter.seek(new Range(), Collections.emptySet(), false);
 
@@ -266,7 +266,7 @@ public void deepCopyCopiesTheSource() throws Exception {
     SortedMapIterator source = new SortedMapIterator(createKeyValues());
 
     RowFilter filter = new RowZeroOrOneFilter();
-    filter.init(source, Collections.emptyMap(), new DefaultIteratorEnvironment());
+    filter.init(source, Collections.emptyMap(), ClientIteratorEnvironment.DEFAULT);
 
     filter.seek(new Range(), Collections.emptySet(), false);
 
@@ -286,7 +286,7 @@ public void deepCopyCopiesTheSource() throws Exception {
     }
 
     // Make a copy of the original RowFilter
-    RowFilter copy = (RowFilter) filter.deepCopy(new DefaultIteratorEnvironment());
+    RowFilter copy = (RowFilter) filter.deepCopy(ClientIteratorEnvironment.DEFAULT);
 
     // Because it's a copy, we should be able to safely seek this one without affecting the original
     copy.seek(new Range(), Collections.emptySet(), false);
diff --git a/core/src/test/java/org/apache/accumulo/core/iterators/user/TransformingIteratorTest.java b/core/src/test/java/org/apache/accumulo/core/iterators/user/TransformingIteratorTest.java
index 6798b6fecfb..a9c9c92c938 100644
--- a/core/src/test/java/org/apache/accumulo/core/iterators/user/TransformingIteratorTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iterators/user/TransformingIteratorTest.java
@@ -47,6 +47,7 @@
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
 import org.apache.accumulo.core.iterators.WrappingIterator;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.iteratorsImpl.system.ColumnFamilySkippingIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.VisibilityFilter;
@@ -619,7 +620,7 @@ public static class ColFamReversingCompactionKeyTransformingIterator
     @Override
     public void init(SortedKeyValueIterator<Key,Value> source, Map<String,String> options,
         IteratorEnvironment env) throws IOException {
-      env = new MajCIteratorEnvironmentAdapter();
+      env = new ClientIteratorEnvironment.Builder().withScope(IteratorScope.majc).build();
       super.init(source, options, env);
     }
   }
@@ -665,7 +666,7 @@ public static class IllegalVisCompactionKeyTransformingIterator
     @Override
     public void init(SortedKeyValueIterator<Key,Value> source, Map<String,String> options,
         IteratorEnvironment env) throws IOException {
-      env = new MajCIteratorEnvironmentAdapter();
+      env = new ClientIteratorEnvironment.Builder().withScope(IteratorScope.majc).build();
       super.init(source, options, env);
     }
   }
@@ -695,7 +696,7 @@ public static class BadVisCompactionKeyTransformingIterator
     @Override
     public void init(SortedKeyValueIterator<Key,Value> source, Map<String,String> options,
         IteratorEnvironment env) throws IOException {
-      env = new MajCIteratorEnvironmentAdapter();
+      env = new ClientIteratorEnvironment.Builder().withScope(IteratorScope.majc).build();
       super.init(source, options, env);
     }
   }
@@ -742,10 +743,4 @@ private void loadTop() {
     }
   }
 
-  private static class MajCIteratorEnvironmentAdapter implements IteratorEnvironment {
-    @Override
-    public IteratorScope getIteratorScope() {
-      return IteratorScope.majc;
-    }
-  }
 }
diff --git a/core/src/test/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtilTest.java b/core/src/test/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtilTest.java
index de5794607c4..251a3441b36 100644
--- a/core/src/test/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtilTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtilTest.java
@@ -39,7 +39,6 @@
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.dataImpl.thrift.IterInfo;
-import org.apache.accumulo.core.iterators.DefaultIteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
@@ -139,8 +138,7 @@ public Value getTopValue() {
   private SortedKeyValueIterator<Key,Value> createIter(IteratorScope scope,
       SortedMapIterator source, AccumuloConfiguration conf) throws IOException {
     var ibEnv = IteratorConfigUtil.loadIterConf(scope, EMPTY_ITERS, new HashMap<>(), conf);
-    var iteratorBuilder =
-        ibEnv.env(new DefaultIteratorEnvironment(conf)).useClassLoader(null).build();
+    var iteratorBuilder = ibEnv.env(ClientIteratorEnvironment.DEFAULT).useClassLoader(null).build();
     return IteratorConfigUtil.loadIterators(source, iteratorBuilder);
   }
 
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java
index 408f9242b43..4794c4f713f 100644
--- a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java
+++ b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/IteratorTestInput.java
@@ -33,7 +33,7 @@
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
-import org.apache.accumulo.iteratortest.environments.SimpleIteratorEnvironment;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 
 /**
  * The necessary user-input to invoke a test on a {@link SortedKeyValueIterator}.
@@ -49,7 +49,7 @@ public class IteratorTestInput {
   private final IteratorEnvironment iteratorEnvironment;
 
   /**
-   * Construct an instance of the test input, using {@link SimpleIteratorEnvironment}.
+   * Construct an instance of the test input, using {@link ClientIteratorEnvironment}.
    *
    * @param iteratorClass The class for the iterator to test.
    * @param iteratorOptions Options, if any, to provide to the iterator ({@link IteratorSetting}'s
@@ -61,7 +61,7 @@ public class IteratorTestInput {
   public IteratorTestInput(Class<? extends SortedKeyValueIterator<Key,Value>> iteratorClass,
       Map<String,String> iteratorOptions, Range range, SortedMap<Key,Value> input) {
     this(iteratorClass, iteratorOptions, range, input, Collections.emptySet(), false,
-        new SimpleIteratorEnvironment());
+        ClientIteratorEnvironment.DEFAULT);
   }
 
   /**
@@ -93,7 +93,7 @@ public IteratorTestInput(Class<? extends SortedKeyValueIterator<Key,Value>> iter
    * @param families Column families passed to {@link SortedKeyValueIterator#seek}.
    * @param inclusive Whether the families are inclusive or exclusive.
    * @param iterEnv An optional provided {@link IteratorEnvironment}.
-   *        {@link SimpleIteratorEnvironment} will be used if null.
+   *        {@link ClientIteratorEnvironment} will be used if null.
    */
   public IteratorTestInput(Class<? extends SortedKeyValueIterator<Key,Value>> iteratorClass,
       Map<String,String> iteratorOptions, Range range, SortedMap<Key,Value> input,
@@ -108,7 +108,7 @@ public IteratorTestInput(Class<? extends SortedKeyValueIterator<Key,Value>> iter
     this.input = Collections.unmodifiableSortedMap(requireNonNull(input));
     this.families = Collections.unmodifiableCollection(requireNonNull(families));
     this.inclusive = inclusive;
-    this.iteratorEnvironment = iterEnv == null ? new SimpleIteratorEnvironment() : iterEnv;
+    this.iteratorEnvironment = iterEnv == null ? ClientIteratorEnvironment.DEFAULT : iterEnv;
   }
 
   public Class<? extends SortedKeyValueIterator<Key,Value>> getIteratorClass() {
diff --git a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/environments/SimpleIteratorEnvironment.java b/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/environments/SimpleIteratorEnvironment.java
deleted file mode 100644
index 0c89197aed8..00000000000
--- a/iterator-test-harness/src/main/java/org/apache/accumulo/iteratortest/environments/SimpleIteratorEnvironment.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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
- *
- *   https://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.accumulo.iteratortest.environments;
-
-import org.apache.accumulo.core.iterators.IteratorEnvironment;
-
-/**
- * A simple implementation of {@link IteratorEnvironment} which is unimplemented.
- */
-public class SimpleIteratorEnvironment implements IteratorEnvironment {
-
-  @Override
-  public boolean isSamplingEnabled() {
-    return false;
-  }
-
-}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java b/server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java
index 63970b2943a..a1b657b5628 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java
@@ -23,6 +23,7 @@
 import java.util.Collections;
 import java.util.Map;
 
+import org.apache.accumulo.core.client.PluginEnvironment;
 import org.apache.accumulo.core.client.SampleNotPresentException;
 import org.apache.accumulo.core.client.sample.SamplerConfiguration;
 import org.apache.accumulo.core.conf.AccumuloConfiguration;
@@ -79,6 +80,30 @@ public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
     this.topLevelIterators = new ArrayList<>();
   }
 
+  public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
+      AccumuloConfiguration tableConfig, TableId tableId, SamplerConfigurationImpl samplerConfig) {
+    if (scope == IteratorScope.majc) {
+      throw new IllegalArgumentException("must set if compaction is full");
+    }
+
+    this.context = context;
+    this.serviceEnvironment = new ServiceEnvironmentImpl(context);
+    this.scope = scope;
+    this.trm = null;
+    this.tableConfig = tableConfig;
+    this.tableId = tableId;
+    this.fullMajorCompaction = false;
+    this.userCompaction = false;
+    this.authorizations = Authorizations.EMPTY;
+    if (samplerConfig != null) {
+      enableSampleForDeepCopy = true;
+      this.samplerConfig = samplerConfig.toSamplerConfiguration();
+    } else {
+      enableSampleForDeepCopy = false;
+    }
+    this.topLevelIterators = new ArrayList<>();
+  }
+
   public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
       AccumuloConfiguration tableConfig, TableId tableId, ScanFileManager trm,
       Map<TabletFile,DataFileValue> files, Authorizations authorizations,
@@ -236,4 +261,9 @@ public ServiceEnvironment getServiceEnv() {
   public TableId getTableId() {
     return tableId;
   }
+
+  @Override
+  public PluginEnvironment getPluginEnv() {
+    return serviceEnvironment;
+  }
 }
diff --git a/server/base/src/test/java/org/apache/accumulo/server/replication/StatusCombinerTest.java b/server/base/src/test/java/org/apache/accumulo/server/replication/StatusCombinerTest.java
index 974617d5e84..faa24bdf736 100644
--- a/server/base/src/test/java/org/apache/accumulo/server/replication/StatusCombinerTest.java
+++ b/server/base/src/test/java/org/apache/accumulo/server/replication/StatusCombinerTest.java
@@ -34,8 +34,8 @@
 import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.iterators.Combiner;
 import org.apache.accumulo.core.iterators.DevNull;
-import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.core.replication.ReplicationSchema.StatusSection;
 import org.apache.accumulo.server.replication.proto.Replication.Status;
 import org.junit.jupiter.api.BeforeEach;
@@ -48,13 +48,6 @@ public class StatusCombinerTest {
   private Key key;
   private Status.Builder builder;
 
-  private static class TestIE implements IteratorEnvironment {
-    @Override
-    public IteratorScope getIteratorScope() {
-      return IteratorScope.scan;
-    }
-  }
-
   @BeforeEach
   public void initCombiner() throws IOException {
     key = new Key();
@@ -62,7 +55,8 @@ public void initCombiner() throws IOException {
     builder = Status.newBuilder();
     IteratorSetting cfg = new IteratorSetting(50, StatusCombiner.class);
     Combiner.setColumns(cfg, Collections.singletonList(new Column(StatusSection.NAME)));
-    combiner.init(new DevNull(), cfg.getOptions(), new TestIE());
+    combiner.init(new DevNull(), cfg.getOptions(),
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.scan).build());
   }
 
   @Test
diff --git a/server/tserver/src/test/java/org/apache/accumulo/tserver/InMemoryMapTest.java b/server/tserver/src/test/java/org/apache/accumulo/tserver/InMemoryMapTest.java
index eefa5c1252b..5bd26d5b74f 100644
--- a/server/tserver/src/test/java/org/apache/accumulo/tserver/InMemoryMapTest.java
+++ b/server/tserver/src/test/java/org/apache/accumulo/tserver/InMemoryMapTest.java
@@ -38,7 +38,6 @@
 import org.apache.accumulo.core.client.SampleNotPresentException;
 import org.apache.accumulo.core.client.sample.RowSampler;
 import org.apache.accumulo.core.client.sample.Sampler;
-import org.apache.accumulo.core.client.sample.SamplerConfiguration;
 import org.apache.accumulo.core.conf.ConfigurationCopy;
 import org.apache.accumulo.core.conf.DefaultConfiguration;
 import org.apache.accumulo.core.conf.Property;
@@ -49,7 +48,7 @@
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.ColumnFamilySkippingIterator;
 import org.apache.accumulo.core.iteratorsImpl.system.IterationInterruptedException;
@@ -59,6 +58,7 @@
 import org.apache.accumulo.core.util.LocalityGroupUtil;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.conf.TableConfiguration;
+import org.apache.accumulo.server.iterators.TabletIteratorEnvironment;
 import org.apache.accumulo.tserver.InMemoryMap.MemoryIterator;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.io.Text;
@@ -71,29 +71,6 @@
 @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "paths not set by user input")
 public class InMemoryMapTest extends WithTestNames {
 
-  private static class SampleIE implements IteratorEnvironment {
-
-    private final SamplerConfiguration sampleConfig;
-
-    public SampleIE() {
-      this.sampleConfig = null;
-    }
-
-    public SampleIE(SamplerConfigurationImpl sampleConfig) {
-      this.sampleConfig = sampleConfig.toSamplerConfiguration();
-    }
-
-    @Override
-    public boolean isSamplingEnabled() {
-      return sampleConfig != null;
-    }
-
-    @Override
-    public SamplerConfiguration getSamplerConfiguration() {
-      return sampleConfig;
-    }
-  }
-
   public static ServerContext getServerContext() {
     Configuration hadoopConf = new Configuration();
     ServerContext context = EasyMock.createMock(ServerContext.class);
@@ -319,7 +296,9 @@ public void test6() throws Exception {
 
     mutate(imm, "r1", "foo:cq5", 3, "bar5");
 
-    SortedKeyValueIterator<Key,Value> dc = ski1.deepCopy(new SampleIE());
+    SortedKeyValueIterator<Key,Value> dc =
+        ski1.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+            getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
 
     ski1.seek(new Range(newKey("r1", "foo:cq1", 3), null), Set.of(), false);
     testAndCallNext(ski1, "r1", "foo:cq1", 3, "bar1");
@@ -371,8 +350,9 @@ private void deepCopyAndDelete(int interleaving, boolean interrupt) throws Excep
       }
     }
 
-    SortedKeyValueIterator<Key,Value> dc = ski1.deepCopy(new SampleIE());
-
+    SortedKeyValueIterator<Key,Value> dc =
+        ski1.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+            getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
     if (interleaving == 2) {
       imm.delete(0);
       if (interrupt) {
@@ -523,7 +503,9 @@ public void testLocalityGroups() throws Exception {
     MemoryIterator iter1 = imm.skvIterator(null);
 
     seekLocalityGroups(iter1);
-    SortedKeyValueIterator<Key,Value> dc1 = iter1.deepCopy(new SampleIE());
+    SortedKeyValueIterator<Key,Value> dc1 =
+        iter1.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+            getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
     seekLocalityGroups(dc1);
 
     assertEquals(10, imm.getNumEntries());
@@ -576,12 +558,27 @@ public void testSample() throws Exception {
 
       MemoryIterator iter1 = imm.skvIterator(sampleConfig);
       MemoryIterator iter2 = imm.skvIterator(null);
-      SortedKeyValueIterator<Key,Value> iter0dc1 = iter0.deepCopy(new SampleIE());
-      SortedKeyValueIterator<Key,Value> iter0dc2 = iter0.deepCopy(new SampleIE(sampleConfig));
-      SortedKeyValueIterator<Key,Value> iter1dc1 = iter1.deepCopy(new SampleIE());
-      SortedKeyValueIterator<Key,Value> iter1dc2 = iter1.deepCopy(new SampleIE(sampleConfig));
-      SortedKeyValueIterator<Key,Value> iter2dc1 = iter2.deepCopy(new SampleIE());
-      SortedKeyValueIterator<Key,Value> iter2dc2 = iter2.deepCopy(new SampleIE(sampleConfig));
+      SortedKeyValueIterator<Key,Value> iter0dc1 =
+          iter0.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+              getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
+      SortedKeyValueIterator<Key,
+          Value> iter0dc2 = iter0.deepCopy(new TabletIteratorEnvironment(getServerContext(),
+              IteratorScope.scan, getServerContext().getTableConfiguration(TableId.of("foo")),
+              TableId.of("foo"), sampleConfig));
+      SortedKeyValueIterator<Key,Value> iter1dc1 =
+          iter1.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+              getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
+      SortedKeyValueIterator<Key,
+          Value> iter1dc2 = iter1.deepCopy(new TabletIteratorEnvironment(getServerContext(),
+              IteratorScope.scan, getServerContext().getTableConfiguration(TableId.of("foo")),
+              TableId.of("foo"), sampleConfig));
+      SortedKeyValueIterator<Key,Value> iter2dc1 =
+          iter2.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+              getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
+      SortedKeyValueIterator<Key,
+          Value> iter2dc2 = iter2.deepCopy(new TabletIteratorEnvironment(getServerContext(),
+              IteratorScope.scan, getServerContext().getTableConfiguration(TableId.of("foo")),
+              TableId.of("foo"), sampleConfig));
 
       assertEquals(expectedNone, readAll(iter0));
       assertEquals(expectedNone, readAll(iter0dc1));
@@ -605,12 +602,27 @@ public void testSample() throws Exception {
       assertEquals(expectedSample, readAll(iter1dc2));
       assertEquals(expectedSample, readAll(iter2dc2));
 
-      SortedKeyValueIterator<Key,Value> iter0dc3 = iter0.deepCopy(new SampleIE());
-      SortedKeyValueIterator<Key,Value> iter0dc4 = iter0.deepCopy(new SampleIE(sampleConfig));
-      SortedKeyValueIterator<Key,Value> iter1dc3 = iter1.deepCopy(new SampleIE());
-      SortedKeyValueIterator<Key,Value> iter1dc4 = iter1.deepCopy(new SampleIE(sampleConfig));
-      SortedKeyValueIterator<Key,Value> iter2dc3 = iter2.deepCopy(new SampleIE());
-      SortedKeyValueIterator<Key,Value> iter2dc4 = iter2.deepCopy(new SampleIE(sampleConfig));
+      SortedKeyValueIterator<Key,Value> iter0dc3 =
+          iter0.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+              getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
+      SortedKeyValueIterator<Key,
+          Value> iter0dc4 = iter0.deepCopy(new TabletIteratorEnvironment(getServerContext(),
+              IteratorScope.scan, getServerContext().getTableConfiguration(TableId.of("foo")),
+              TableId.of("foo"), sampleConfig));
+      SortedKeyValueIterator<Key,Value> iter1dc3 =
+          iter1.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+              getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
+      SortedKeyValueIterator<Key,
+          Value> iter1dc4 = iter1.deepCopy(new TabletIteratorEnvironment(getServerContext(),
+              IteratorScope.scan, getServerContext().getTableConfiguration(TableId.of("foo")),
+              TableId.of("foo"), sampleConfig));
+      SortedKeyValueIterator<Key,Value> iter2dc3 =
+          iter2.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+              getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo")));
+      SortedKeyValueIterator<Key,
+          Value> iter2dc4 = iter2.deepCopy(new TabletIteratorEnvironment(getServerContext(),
+              IteratorScope.scan, getServerContext().getTableConfiguration(TableId.of("foo")),
+              TableId.of("foo"), sampleConfig));
 
       assertEquals(expectedNone, readAll(iter0dc3));
       assertEquals(expectedNone, readAll(iter0dc4));
@@ -667,7 +679,9 @@ private void runInterruptSampleTest(boolean deepCopy, boolean delete, boolean dc
     }
 
     if (deepCopy) {
-      iter = iter.deepCopy(new SampleIE(sampleConfig1));
+      iter = iter.deepCopy(new TabletIteratorEnvironment(getServerContext(), IteratorScope.scan,
+          getServerContext().getTableConfiguration(TableId.of("foo")), TableId.of("foo"),
+          sampleConfig1));
     }
 
     if (delete && dcAfterDelete) {
@@ -768,7 +782,10 @@ public void testDeferredSamplerCreation() throws Exception {
     iter.seek(new Range(), Set.of(), false);
     assertEquals(expectedSample, readAll(iter));
 
-    SortedKeyValueIterator<Key,Value> dc = iter.deepCopy(new SampleIE(sampleConfig2));
+    SortedKeyValueIterator<Key,
+        Value> dc = iter.deepCopy(new TabletIteratorEnvironment(getServerContext(),
+            IteratorScope.scan, getServerContext().getTableConfiguration(TableId.of("foo")),
+            TableId.of("foo"), sampleConfig2));
     dc.seek(new Range(), Set.of(), false);
     assertEquals(expectedSample, readAll(dc));
 
diff --git a/test/src/main/java/org/apache/accumulo/test/performance/scan/CollectTabletStats.java b/test/src/main/java/org/apache/accumulo/test/performance/scan/CollectTabletStats.java
index 63ec7fcf113..e34747e3aea 100644
--- a/test/src/main/java/org/apache/accumulo/test/performance/scan/CollectTabletStats.java
+++ b/test/src/main/java/org/apache/accumulo/test/performance/scan/CollectTabletStats.java
@@ -52,7 +52,6 @@
 import org.apache.accumulo.core.dataImpl.thrift.IterInfo;
 import org.apache.accumulo.core.file.FileOperations;
 import org.apache.accumulo.core.file.FileSKVIterator;
-import org.apache.accumulo.core.iterators.IteratorEnvironment;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
 import org.apache.accumulo.core.iteratorsImpl.IteratorConfigUtil;
@@ -73,6 +72,7 @@
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.conf.TableConfiguration;
 import org.apache.accumulo.server.fs.VolumeManager;
+import org.apache.accumulo.server.iterators.TabletIteratorEnvironment;
 import org.apache.accumulo.server.util.MetadataTableUtil;
 import org.apache.hadoop.fs.BlockLocation;
 import org.apache.hadoop.fs.FileStatus;
@@ -103,8 +103,6 @@ static class CollectOptions extends ServerUtilOpts {
     String columns;
   }
 
-  static class TestEnvironment implements IteratorEnvironment {}
-
   public static void main(String[] args) throws Exception {
 
     final CollectOptions opts = new CollectOptions();
@@ -429,8 +427,8 @@ private static void reportHdfsBlockLocations(ServerContext context, List<TabletF
   private static SortedKeyValueIterator<Key,Value> createScanIterator(KeyExtent ke,
       Collection<SortedKeyValueIterator<Key,Value>> mapfiles, Authorizations authorizations,
       byte[] defaultLabels, HashSet<Column> columnSet, List<IterInfo> ssiList,
-      Map<String,Map<String,String>> ssio, boolean useTableIterators, TableConfiguration conf)
-      throws IOException {
+      Map<String,Map<String,String>> ssio, boolean useTableIterators, TableConfiguration conf,
+      ServerContext context) throws IOException {
 
     SortedMapIterator smi = new SortedMapIterator(new TreeMap<>());
 
@@ -449,7 +447,9 @@ private static SortedKeyValueIterator<Key,Value> createScanIterator(KeyExtent ke
 
     if (useTableIterators) {
       var ibEnv = IteratorConfigUtil.loadIterConf(IteratorScope.scan, ssiList, ssio, conf);
-      var iteratorBuilder = ibEnv.env(new TestEnvironment()).useClassLoader("test").build();
+      TabletIteratorEnvironment iterEnv =
+          new TabletIteratorEnvironment(context, IteratorScope.scan, conf, ke.tableId());
+      var iteratorBuilder = ibEnv.env(iterEnv).useClassLoader("test").build();
       return IteratorConfigUtil.loadIterators(visFilter, iteratorBuilder);
     }
     return visFilter;
@@ -506,7 +506,7 @@ private static int readFilesUsingIterStack(VolumeManager fs, ServerContext conte
     Map<String,Map<String,String>> emptySsio = Collections.emptyMap();
     TableConfiguration tconf = context.getTableConfiguration(ke.tableId());
     reader = createScanIterator(ke, readers, auths, new byte[] {}, new HashSet<>(), emptyIterinfo,
-        emptySsio, useTableIterators, tconf);
+        emptySsio, useTableIterators, tconf, context);
 
     HashSet<ByteSequence> columnSet = createColumnBSS(columns);
 
diff --git a/test/src/test/java/org/apache/accumulo/test/iterator/SummingCombinerTest.java b/test/src/test/java/org/apache/accumulo/test/iterator/SummingCombinerTest.java
index 7d8b21519f0..0bbb6a85139 100644
--- a/test/src/test/java/org/apache/accumulo/test/iterator/SummingCombinerTest.java
+++ b/test/src/test/java/org/apache/accumulo/test/iterator/SummingCombinerTest.java
@@ -32,11 +32,11 @@
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.LongCombiner;
 import org.apache.accumulo.core.iterators.user.SummingCombiner;
+import org.apache.accumulo.core.iteratorsImpl.ClientIteratorEnvironment;
 import org.apache.accumulo.iteratortest.IteratorTestBase;
 import org.apache.accumulo.iteratortest.IteratorTestInput;
 import org.apache.accumulo.iteratortest.IteratorTestOutput;
 import org.apache.accumulo.iteratortest.IteratorTestParameters;
-import org.apache.accumulo.iteratortest.environments.SimpleIteratorEnvironment;
 
 /**
  * Iterator test harness tests for SummingCombiner
@@ -48,14 +48,8 @@ public class SummingCombinerTest extends IteratorTestBase {
 
   @Override
   protected Stream<IteratorTestParameters> parameters() {
-    var env = new SimpleIteratorEnvironment() {
-      @Override
-      public IteratorScope getIteratorScope() {
-        return IteratorScope.majc;
-      }
-    };
-    var input =
-        new IteratorTestInput(SummingCombiner.class, createOpts(), new Range(), INPUT_DATA, env);
+    var input = new IteratorTestInput(SummingCombiner.class, createOpts(), new Range(), INPUT_DATA,
+        new ClientIteratorEnvironment.Builder().withScope(IteratorScope.majc).build());
     var expectedOutput = new IteratorTestOutput(OUTPUT_DATA);
     return builtinTestCases().map(test -> test.toParameters(input, expectedOutput));
   }


Reviews:
user: login: kevinrr888
body: Have not finished reviewing everything; want to first ensure this is a PR we want to move forward with. Overall, I think these changes are a good idea, however, this refactoring can very easily have bugs that are hard to spot. I'm not sure if this is the best for a 2.1 change, risking potentially introducing more bugs than the original PR set out to fix.

Some additional comments:
- IteratorEnvIT may need to be changed (would be nice to include the changes to that IT from #4816 here as well)
- There may still be some other things from #4816 that we still want
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T18:56:57Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:02:52Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:26:20Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:29:52Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:49:26Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:49:52Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:51:39Z
user: login: keith-turner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T00:22:58Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T15:47:59Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:23:36Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:23:44Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:23:49Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:23:55Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:24:05Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:24:21Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:51:56Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:52:03Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:52:09Z
user: login: keith-turner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T21:54:07Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T14:15:08Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T14:45:52Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T15:19:42Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T15:20:04Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T15:20:30Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T15:20:57Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T15:21:09Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T15:21:48Z
user: login: keith-turner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T20:00:32Z
user: login: keith-turner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T20:04:32Z
user: login: keith-turner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T20:29:04Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T20:35:43Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T20:38:08Z
user: login: kevinrr888
body: Posting what I have now. Will finish review tomorrow
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T21:08:03Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T21:10:54Z
user: login: ctubbsii
body: I'm in favor of this change, overall. There is some small risk of doing this in a bugfix release, but I don't see any public API changes, or even SPI changes, that would really impact users. As long as all the ITs pass, I'm in favor of this overall. Aside from that, I don't have anything to add that others haven't already said or have already been fixed in the implementation.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T21:25:21Z
user: login: keith-turner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:11:50Z
user: login: keith-turner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:14:10Z
user: login: keith-turner
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:14:29Z
user: login: kevinrr888
body: These changes look good and are a lot nicer than the previous impl. Some follow ons for this may be:
* https://github.com/apache/accumulo/pull/5490#discussion_r2056825666 - I did not understand this constructor and why the builder couldn't be used, but since it's just used in tests and those pass, not too important. If the constructor is kept, could potentially add `VisibleForTesting` tag
* #4816 - Should extract the useful bits from this PR and merge in separately (I can do this). Most importantly, want the `IteratorEnvIT` changes which will test the changes done here.
* Look at potentially replacing the `RFileScanner` table id with null or something better if possible.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T16:14:08Z
user: login: dlmarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T17:37:16Z

Review comments:
diff_hunk: @@ -295,9 +230,10 @@ public Iterator<Entry<Key,Value>> iterator() {
 
     SortedKeyValueIterator<Key,Value> skvi;
     try {
-      IteratorEnvironment iterEnv = new ClientSideIteratorEnvironment(
-          getSamplerConfiguration() != null, getIteratorSamplerConfigurationInternal());
-
+      IteratorEnvironment iterEnv = new ClientIteratorEnvironment.Builder()
+          .withClient(context.get()).withAuthorizations(getAuthorizations())
+          .withScope(IteratorScope.scan).withTableId(tableId.get())
+          .withSamplerConfiguration(getIteratorSamplerConfigurationInternal()).build();
path: core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
user: login: kevinrr888
body: Just want to confirm functionality is same before and after. Originally, was passing `getSamplerConfiguration() != null` which would be the return value of `isSamplingEnabled()`. Now, `isSamplingEnabled()` will always return true (since the `samplerConfig` will always be present with value `getIteratorSamplerConfigurationInternal()`)

Essentially, previously was using both `getSamplerConfiguration()` and `getIteratorSamplerConfigurationInternal()` but is now just using `getIteratorSamplerConfigurationInternal()`
created_at: 2025-04-21T16:37:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (!isSamplingEnabled()) {
+      throw new SampleNotPresentException();
+    }
+    return new ClientIteratorEnvironment(this);
+  }
+
+  @Override
+  public boolean isSamplingEnabled() {
+    return this.samplerConfig.isPresent();
+  }
+
+  @Override
+  public SamplerConfiguration getSamplerConfiguration() {
+    return samplerConfig.orElse(null);
+  }
+
+  @Override
+  public boolean isUserCompaction() {
+    if (getIteratorScope() == IteratorScope.scan) {
+      throw new IllegalStateException(
+          "scan iterator scope is incompatible with a possible user compaction");
+    }
+    return this.isUserCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.1.0")
+  public ServiceEnvironment getServiceEnv() {
+    return env.orElseThrow();
+  }
+
+  @Override
+  public PluginEnvironment getPluginEnv() {
+    return getServiceEnv();
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: kevinrr888
body: ```suggestion
    return env.orElseThrow();
```
Think it would be better to avoid calling the deprecated method for when it's dropped
created_at: 2025-04-21T16:47:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: kevinrr888
body: Could this be changed to take ClientContext to avoid casting? Seems like what is passed is already always ClientContext
created_at: 2025-04-21T16:57:02Z
author_association: MEMBER
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: Might be nicer to avoid anon class
created_at: 2025-04-21T17:13:28Z
author_association: MEMBER
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
+
+        @Override
+        public String getTableName(TableId tableId) throws TableNotFoundException {
+          throw new IllegalStateException("TableId not known in RFileScanner");
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: ```
private static final String errorMsg =
      "This scanner is unrelated to any table or accumulo instance;"
          + " it operates directly on files. Therefore, it can not support this operation.";
```
Is the error message used in #4816, could keep this or something similar since it's more descriptive
created_at: 2025-04-21T17:20:38Z
author_association: MEMBER
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
+
+        @Override
+        public String getTableName(TableId tableId) throws TableNotFoundException {
+          throw new IllegalStateException("TableId not known in RFileScanner");
+        }
+
+        @Override
+        public <T> T instantiate(String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+              .getDeclaredConstructor().newInstance();
+        }
+
+        @Override
+        public <T> T instantiate(TableId tableId, String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return instantiate(className, base);
+        }
+
+        @Override
+        public Configuration getConfiguration() {
+          return new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+        }
+
+        @Override
+        public Configuration getConfiguration(TableId tableId) {
+          return new ConfigurationImpl(new ConfigurationCopy(opts.tableConfig));
+        }
+
+      };
+
+      ClientIteratorEnvironment.Builder iterEnvBuilder =
+          new RFileScannerIteratorEnvironmentBuilder().withEnvironment(senv)
+              .withAuthorizations(opts.auths).withScope(IteratorScope.scan);
+      if (getSamplerConfiguration() != null) {
+        iterEnvBuilder.withSamplerConfiguration(getSamplerConfiguration());
+      }
+      IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: I believe this needs a `withTableId`. While a table id doesn't make sense in this case, the use case would be:
`env.getPluginEnv().getConfiguration(env.getTableId())`
See discussions: https://github.com/apache/accumulo/issues/4810 and https://github.com/apache/accumulo/pull/4816/files#r1725159871 and https://github.com/apache/accumulo/pull/4816#issuecomment-2347164444 (2nd section)
created_at: 2025-04-21T17:40:45Z
author_association: MEMBER
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
+
+        @Override
+        public String getTableName(TableId tableId) throws TableNotFoundException {
+          throw new IllegalStateException("TableId not known in RFileScanner");
+        }
+
+        @Override
+        public <T> T instantiate(String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+              .getDeclaredConstructor().newInstance();
+        }
+
+        @Override
+        public <T> T instantiate(TableId tableId, String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return instantiate(className, base);
+        }
+
+        @Override
+        public Configuration getConfiguration() {
+          return new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+        }
+
+        @Override
+        public Configuration getConfiguration(TableId tableId) {
+          return new ConfigurationImpl(new ConfigurationCopy(opts.tableConfig));
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: When the `withTableId` is added, could change this to ensure the tableId is the same:
```
Preconditions.checkArgument(tableId == getTableId(),
              "Expected tableId obtained from IteratorEnvironment.getTableId() but got " + tableId
                  + " when requesting the table config");
```
(copied from #4816)
created_at: 2025-04-21T17:50:57Z
author_association: MEMBER
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
+
+        @Override
+        public String getTableName(TableId tableId) throws TableNotFoundException {
+          throw new IllegalStateException("TableId not known in RFileScanner");
+        }
+
+        @Override
+        public <T> T instantiate(String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+              .getDeclaredConstructor().newInstance();
+        }
+
+        @Override
+        public <T> T instantiate(TableId tableId, String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return instantiate(className, base);
+        }
+
+        @Override
+        public Configuration getConfiguration() {
+          return new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+        }
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: In #4816, had this method throwing an exception. Don't remember the specifics, but should make sure this functions as we expect (should it throw an exception, or return something)
created_at: 2025-04-21T17:54:32Z
author_association: MEMBER
diff_hunk: @@ -345,9 +245,13 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
 
     MultiIterator multiIter = new MultiIterator(readers, extent);
 
-    OfflineIteratorEnvironment iterEnv =
-        new OfflineIteratorEnvironment(context, tableId, authorizations, tableCC, false,
-            samplerConfImpl == null ? null : samplerConfImpl.toSamplerConfiguration());
+    ClientIteratorEnvironment.Builder iterEnvBuilder =
+        new ClientIteratorEnvironment.Builder().withAuthorizations(authorizations)
+            .withScope(IteratorScope.scan).withTableId(tableId).withClient(context);
+    if (scannerSamplerConfig != null) {
+      iterEnvBuilder.withSamplerConfiguration(scannerSamplerConfig);
+    }
+    IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
user: login: kevinrr888
body: It seems usage of `tableCC` and `samplerConfImpl` were dropped. Was this intentional?
created_at: 2025-04-21T18:07:06Z
author_association: MEMBER
diff_hunk: @@ -345,9 +245,13 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
 
     MultiIterator multiIter = new MultiIterator(readers, extent);
 
-    OfflineIteratorEnvironment iterEnv =
-        new OfflineIteratorEnvironment(context, tableId, authorizations, tableCC, false,
-            samplerConfImpl == null ? null : samplerConfImpl.toSamplerConfiguration());
+    ClientIteratorEnvironment.Builder iterEnvBuilder =
+        new ClientIteratorEnvironment.Builder().withAuthorizations(authorizations)
+            .withScope(IteratorScope.scan).withTableId(tableId).withClient(context);
+    if (scannerSamplerConfig != null) {
+      iterEnvBuilder.withSamplerConfiguration(scannerSamplerConfig);
+    }
+    IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
user: login: kevinrr888
body: It also seems like `isSamplingEnabled()` is always false in original impl, but not in this impl
created_at: 2025-04-21T18:10:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: I had that earlier, and it fails the build. `o.a.a.core.iterators` is in the public API, so this method needs to use AccumuloClient vs ClientContext as the API parameter.
created_at: 2025-04-21T19:02:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -345,9 +245,13 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
 
     MultiIterator multiIter = new MultiIterator(readers, extent);
 
-    OfflineIteratorEnvironment iterEnv =
-        new OfflineIteratorEnvironment(context, tableId, authorizations, tableCC, false,
-            samplerConfImpl == null ? null : samplerConfImpl.toSamplerConfiguration());
+    ClientIteratorEnvironment.Builder iterEnvBuilder =
+        new ClientIteratorEnvironment.Builder().withAuthorizations(authorizations)
+            .withScope(IteratorScope.scan).withTableId(tableId).withClient(context);
+    if (scannerSamplerConfig != null) {
+      iterEnvBuilder.withSamplerConfiguration(scannerSamplerConfig);
+    }
+    IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
user: login: dlmarion
body: The old code set useSampling to `false`, but went to the trouble of creating the `sampleConfiguration`. I'm wondering if setting `useSampling` to `false` was a bug in the old version.

https://github.com/apache/accumulo/blob/3912b36d0b0ca103b7068d7c7575c99d32a11a97/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java#L324-L331
created_at: 2025-04-21T19:26:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -345,9 +245,13 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
 
     MultiIterator multiIter = new MultiIterator(readers, extent);
 
-    OfflineIteratorEnvironment iterEnv =
-        new OfflineIteratorEnvironment(context, tableId, authorizations, tableCC, false,
-            samplerConfImpl == null ? null : samplerConfImpl.toSamplerConfiguration());
+    ClientIteratorEnvironment.Builder iterEnvBuilder =
+        new ClientIteratorEnvironment.Builder().withAuthorizations(authorizations)
+            .withScope(IteratorScope.scan).withTableId(tableId).withClient(context);
+    if (scannerSamplerConfig != null) {
+      iterEnvBuilder.withSamplerConfiguration(scannerSamplerConfig);
+    }
+    IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
user: login: dlmarion
body: Regarding the missing `tableCC`, by setting `withClient(ClientContext)`, it will create a `ClientServiceEnvironmentImpl` for the calls to ` getServiceEnv` and `getPluginEnv` and the table configuration can be retrieved from that object.
created_at: 2025-04-21T19:29:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -295,9 +230,10 @@ public Iterator<Entry<Key,Value>> iterator() {
 
     SortedKeyValueIterator<Key,Value> skvi;
     try {
-      IteratorEnvironment iterEnv = new ClientSideIteratorEnvironment(
-          getSamplerConfiguration() != null, getIteratorSamplerConfigurationInternal());
-
+      IteratorEnvironment iterEnv = new ClientIteratorEnvironment.Builder()
+          .withClient(context.get()).withAuthorizations(getAuthorizations())
+          .withScope(IteratorScope.scan).withTableId(tableId.get())
+          .withSamplerConfiguration(getIteratorSamplerConfigurationInternal()).build();
path: core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
user: login: dlmarion
body: I think I fixed this in c14c35f
created_at: 2025-04-21T19:49:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: Moved this to a static inner class in c14c35f
created_at: 2025-04-21T19:49:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -345,9 +245,13 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
 
     MultiIterator multiIter = new MultiIterator(readers, extent);
 
-    OfflineIteratorEnvironment iterEnv =
-        new OfflineIteratorEnvironment(context, tableId, authorizations, tableCC, false,
-            samplerConfImpl == null ? null : samplerConfImpl.toSamplerConfiguration());
+    ClientIteratorEnvironment.Builder iterEnvBuilder =
+        new ClientIteratorEnvironment.Builder().withAuthorizations(authorizations)
+            .withScope(IteratorScope.scan).withTableId(tableId).withClient(context);
+    if (scannerSamplerConfig != null) {
+      iterEnvBuilder.withSamplerConfiguration(scannerSamplerConfig);
+    }
+    IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
user: login: dlmarion
body: Fixed the `samplerConfImpl` issue in c14c35f
created_at: 2025-04-21T19:51:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: The way this is constructed, the stack trace would likely not be correct when its thrown.  Suspect it would not reflect the stack trace of the thread calling this method, but rather the stack of the thread that loaded the class and created the static singleton.
created_at: 2025-04-21T23:46:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: Optional objects are immutable so do not need to copy.

```suggestion
    this.tableId = copy.tableId;
```
created_at: 2025-04-22T00:03:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not majc");
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: ```suggestion
      throw new IllegalStateException("Iterator scope is not scan");
```
created_at: 2025-04-22T00:05:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (!isSamplingEnabled()) {
+      throw new SampleNotPresentException();
+    }
+    return new ClientIteratorEnvironment(this);
+  }
+
+  @Override
+  public boolean isSamplingEnabled() {
+    return this.samplerConfig.isPresent();
+  }
+
+  @Override
+  public SamplerConfiguration getSamplerConfiguration() {
+    return samplerConfig.orElse(null);
+  }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: Sampling being enabled and the config being present are separate.  An iterator w/o sampling enabled initially can create deep copy over sample data.   For this to work, can have an iter env that has sample config but does not have sampling enabled.  To create the deep copy cloneWithSamplingEnabled() is called which returns a new iter env w/ the sampling config and sampling enabled.

So would need a `boolean isSamplingEnabled` instance variable on this class.  

```suggestion
  public IteratorEnvironment cloneWithSamplingEnabled() {
    if (!samplerConfig.isPresent()) {
      throw new SampleNotPresentException();
    }
    // return a new iter env that is identical in every way except it has sampling enabled.
    return new ClientIteratorEnvironment(this, true);
  }

  @Override
  public boolean isSamplingEnabled() {
    return isSamplingEnabled;
  }

  @Override
  public SamplerConfiguration getSamplerConfiguration() {
    if(!isSamplingEnabled){
       // javadoc mentions null should be returned when sampling is not enabled
       return null;
    }
    return samplerConfig.orElseThrow();
  }
```
created_at: 2025-04-22T00:21:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (!isSamplingEnabled()) {
+      throw new SampleNotPresentException();
+    }
+    return new ClientIteratorEnvironment(this);
+  }
+
+  @Override
+  public boolean isSamplingEnabled() {
+    return this.samplerConfig.isPresent();
+  }
+
+  @Override
+  public SamplerConfiguration getSamplerConfiguration() {
+    return samplerConfig.orElse(null);
+  }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: > Sampling being enabled and the config being present are separate. An iterator w/o sampling enabled initially can create deep copy over sample data. For this to work, can have an iter env that has sample config but does not have sampling enabled. To create the deep copy cloneWithSamplingEnabled() is called which returns a new iter env w/ the sampling config and sampling enabled.

Thanks for that, I wasn't sure, but that makes some of the things I saw in the code make more sense.
created_at: 2025-04-22T15:47:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (!isSamplingEnabled()) {
+      throw new SampleNotPresentException();
+    }
+    return new ClientIteratorEnvironment(this);
+  }
+
+  @Override
+  public boolean isSamplingEnabled() {
+    return this.samplerConfig.isPresent();
+  }
+
+  @Override
+  public SamplerConfiguration getSamplerConfiguration() {
+    return samplerConfig.orElse(null);
+  }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: Fixed in 3cec5f7
created_at: 2025-04-22T17:23:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not majc");
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: Fixed in 3cec5f7
created_at: 2025-04-22T17:23:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: Fixed in 3cec5f7
created_at: 2025-04-22T17:23:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.of(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  private static final UnsupportedOperationException UOE =
+      new UnsupportedOperationException("Feature not supported");
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+  }
+
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope.isPresent() ? Optional.of(copy.scope.orElseThrow()) : Optional.empty();
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths.isPresent() ? Optional.of(copy.auths.orElseThrow()) : Optional.empty();
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId =
+        copy.tableId.isPresent() ? Optional.of(copy.tableId.orElseThrow()) : Optional.empty();
+    this.samplerConfig = copy.samplerConfig.isPresent()
+        ? Optional.of(copy.samplerConfig.orElseThrow()) : Optional.empty();
+    this.env = copy.env.isPresent() ? Optional.of(copy.env.orElseThrow()) : Optional.empty();
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw UOE;
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: Fixed in 3cec5f7
created_at: 2025-04-22T17:23:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -345,9 +245,13 @@ private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent,
 
     MultiIterator multiIter = new MultiIterator(readers, extent);
 
-    OfflineIteratorEnvironment iterEnv =
-        new OfflineIteratorEnvironment(context, tableId, authorizations, tableCC, false,
-            samplerConfImpl == null ? null : samplerConfImpl.toSamplerConfiguration());
+    ClientIteratorEnvironment.Builder iterEnvBuilder =
+        new ClientIteratorEnvironment.Builder().withAuthorizations(authorizations)
+            .withScope(IteratorScope.scan).withTableId(tableId).withClient(context);
+    if (scannerSamplerConfig != null) {
+      iterEnvBuilder.withSamplerConfiguration(scannerSamplerConfig);
+    }
+    IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
user: login: dlmarion
body: Fixed in 3cec5f7
created_at: 2025-04-22T17:24:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -295,9 +230,10 @@ public Iterator<Entry<Key,Value>> iterator() {
 
     SortedKeyValueIterator<Key,Value> skvi;
     try {
-      IteratorEnvironment iterEnv = new ClientSideIteratorEnvironment(
-          getSamplerConfiguration() != null, getIteratorSamplerConfigurationInternal());
-
+      IteratorEnvironment iterEnv = new ClientIteratorEnvironment.Builder()
+          .withClient(context.get()).withAuthorizations(getAuthorizations())
+          .withScope(IteratorScope.scan).withTableId(tableId.get())
+          .withSamplerConfiguration(getIteratorSamplerConfigurationInternal()).build();
path: core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
user: login: dlmarion
body: Fixed in 3cec5f7
created_at: 2025-04-22T17:24:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
+
+        @Override
+        public String getTableName(TableId tableId) throws TableNotFoundException {
+          throw new IllegalStateException("TableId not known in RFileScanner");
+        }
+
+        @Override
+        public <T> T instantiate(String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+              .getDeclaredConstructor().newInstance();
+        }
+
+        @Override
+        public <T> T instantiate(TableId tableId, String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return instantiate(className, base);
+        }
+
+        @Override
+        public Configuration getConfiguration() {
+          return new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+        }
+
+        @Override
+        public Configuration getConfiguration(TableId tableId) {
+          return new ConfigurationImpl(new ConfigurationCopy(opts.tableConfig));
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: Fixed in bf294a4
created_at: 2025-04-22T17:51:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
+
+        @Override
+        public String getTableName(TableId tableId) throws TableNotFoundException {
+          throw new IllegalStateException("TableId not known in RFileScanner");
+        }
+
+        @Override
+        public <T> T instantiate(String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+              .getDeclaredConstructor().newInstance();
+        }
+
+        @Override
+        public <T> T instantiate(TableId tableId, String className, Class<T> base)
+            throws ReflectiveOperationException, IOException {
+          return instantiate(className, base);
+        }
+
+        @Override
+        public Configuration getConfiguration() {
+          return new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+        }
+
+        @Override
+        public Configuration getConfiguration(TableId tableId) {
+          return new ConfigurationImpl(new ConfigurationCopy(opts.tableConfig));
+        }
+
+      };
+
+      ClientIteratorEnvironment.Builder iterEnvBuilder =
+          new RFileScannerIteratorEnvironmentBuilder().withEnvironment(senv)
+              .withAuthorizations(opts.auths).withScope(IteratorScope.scan);
+      if (getSamplerConfiguration() != null) {
+        iterEnvBuilder.withSamplerConfiguration(getSamplerConfiguration());
+      }
+      IteratorEnvironment iterEnv = iterEnvBuilder.build();
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: Fixed in bf294a4
created_at: 2025-04-22T17:52:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -292,15 +280,54 @@ public Iterator<Entry<Key,Value>> iterator() {
             EMPTY_BYTES, tableConf);
       }
 
+      ClientServiceEnvironmentImpl senv = new ClientServiceEnvironmentImpl(null) {
+
+        @Override
+        public String getTableName(TableId tableId) throws TableNotFoundException {
+          throw new IllegalStateException("TableId not known in RFileScanner");
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: Fixed in bf294a4
created_at: 2025-04-22T17:52:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +79,65 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerIteratorEnvironmentBuilder
+      extends ClientIteratorEnvironment.Builder {
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: keith-turner
body: Could push this method up into `ClientIteratorEnvironment.Builder` and then would not need this specialized class.  Also may be able to generlize the type on the method to.

```
public ClientIteratorEnvironment.Builder withEnvironment(ServiceEnvironment env)
```
created_at: 2025-04-22T20:38:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not scan");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    String samplerClass =
+        getPluginEnv().getConfiguration(getTableId()).get(Property.TABLE_SAMPLER.getKey());
+    if (samplerClass == null || samplerClass.isBlank()) {
+      throw new SampleNotPresentException();
+    }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: The following seems good for ClientSideIteratorScanner and is tested in `SampleIT.testIterator()`.

```suggestion
    if (samplerConfig.isEmpty()) {
      throw new SampleNotPresentException();
    }
```

Inspecting table props seems like the right way to go, however the bit of code here is not complete for that.  Looking into RFileScanner it may benefit from a secondary check of tables props.  AFIACT there are no tests for calling `cloneWithSamplingEnabled()` when using RFileScanner. In a follow on PR I could add unit test that cover calling this code when using RFileScanner and maybe add some code that inspects the table config as a secondary step when samplerConfig.isEmpty().

created_at: 2025-04-22T21:19:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: Is this class intended to be in the public API?  This package is in the public API.  Would need javadoc and since tags if that is the intent.  Seems like all of its public methods only use public API types, so it could be in the public API.

If this were in the public API, then it seems like it would be useful for users writing unit test.  Would be good to kick the tires on that concept before releasing and try using on an external project in unit test to see if there any hiccups.  Seems like it may work nicely w/ the following for external unit test.

https://github.com/apache/accumulo/blob/9eda6d28f469fef0844eeb6ff4c765a2896cb41c/core/src/main/java/org/apache/accumulo/core/client/PluginEnvironment.java#L145

However those changes are in main.
created_at: 2025-04-22T21:27:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: Was this implementation based on something else?  On the server side this method can open an rfile or a mapfile (Looking at TabletIteratorEnvironment).  According to my IDE nothing calls this method, so no test.  Since this method has zero test, we may be better off throwing UnsupportedOperationException for now.  Should probably test it if we add an impl.
created_at: 2025-04-22T21:33:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +79,65 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerIteratorEnvironmentBuilder
+      extends ClientIteratorEnvironment.Builder {
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      this.env = Optional.of(env);
+      return this;
+    }
+
+  }
+
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Opts opts;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      this.opts = opts;
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public <T> T instantiate(String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+          .getDeclaredConstructor().newInstance();
+    }
+
+    @Override
+    public <T> T instantiate(TableId tableId, String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return instantiate(className, base);
+    }
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: keith-turner
body: These seem similar enough to the methods in the super class that they could be dropped here.

```suggestion
```
created_at: 2025-04-22T21:50:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +79,65 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerIteratorEnvironmentBuilder
+      extends ClientIteratorEnvironment.Builder {
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      this.env = Optional.of(env);
+      return this;
+    }
+
+  }
+
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Opts opts;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      this.opts = opts;
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public <T> T instantiate(String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+          .getDeclaredConstructor().newInstance();
+    }
+
+    @Override
+    public <T> T instantiate(TableId tableId, String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return instantiate(className, base);
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+      return new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+    }
+
+    @Override
+    public Configuration getConfiguration(TableId tableId) {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      ConfigurationCopy tableCC = new ConfigurationCopy(DefaultConfiguration.getInstance());
+      opts.tableConfig.forEach(tableCC::set);
+      return new ConfigurationImpl(tableCC);
+    }
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: keith-turner
body: Could memorize these or create them once in the constructor.  Sometimes calling code will repeatedly call these methods for each property.
created_at: 2025-04-22T21:53:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +79,65 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerIteratorEnvironmentBuilder
+      extends ClientIteratorEnvironment.Builder {
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: IIRC I had it like this at first and it failed the build because `ClientIteratorEnvionment` is in an API package and `ClientServiceEnvironmentImpl` is not. This is why `ClientIteratorEnvironment.withClient` exists instead.
created_at: 2025-04-23T14:15:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: It came from core/test/.../DefaultIteratorEnvironment. I also found no references to this method, but wasn't sure if we should try and implement something, or throw an exception. I'm good with changing this to throwing an exception.
created_at: 2025-04-23T14:45:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +79,65 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerIteratorEnvironmentBuilder
+      extends ClientIteratorEnvironment.Builder {
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: In 2d39e46 I moved ClientIteratorEnvironment out of the public api package, and moved this method into it.
created_at: 2025-04-23T15:19:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not scan");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    String samplerClass =
+        getPluginEnv().getConfiguration(getTableId()).get(Property.TABLE_SAMPLER.getKey());
+    if (samplerClass == null || samplerClass.isBlank()) {
+      throw new SampleNotPresentException();
+    }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: Implemented in 2d39e46.
created_at: 2025-04-23T15:20:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: I moved ClientIteratorEnvironment out of the public API in 2d39e46.
created_at: 2025-04-23T15:20:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: dlmarion
body: Modified this in 2d39e46 to throw an UnsupportedOperationException
created_at: 2025-04-23T15:20:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +79,65 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerIteratorEnvironmentBuilder
+      extends ClientIteratorEnvironment.Builder {
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      this.env = Optional.of(env);
+      return this;
+    }
+
+  }
+
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Opts opts;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      this.opts = opts;
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public <T> T instantiate(String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+          .getDeclaredConstructor().newInstance();
+    }
+
+    @Override
+    public <T> T instantiate(TableId tableId, String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return instantiate(className, base);
+    }
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: Removed these in 2d39e46.
created_at: 2025-04-23T15:21:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +79,65 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerIteratorEnvironmentBuilder
+      extends ClientIteratorEnvironment.Builder {
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      this.env = Optional.of(env);
+      return this;
+    }
+
+  }
+
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Opts opts;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      this.opts = opts;
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public <T> T instantiate(String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return RFileScanner.class.getClassLoader().loadClass(className).asSubclass(base)
+          .getDeclaredConstructor().newInstance();
+    }
+
+    @Override
+    public <T> T instantiate(TableId tableId, String className, Class<T> base)
+        throws ReflectiveOperationException, IOException {
+      return instantiate(className, base);
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+      return new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+    }
+
+    @Override
+    public Configuration getConfiguration(TableId tableId) {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      ConfigurationCopy tableCC = new ConfigurationCopy(DefaultConfiguration.getInstance());
+      opts.tableConfig.forEach(tableCC::set);
+      return new ConfigurationImpl(tableCC);
+    }
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: dlmarion
body: Moved the configuration calculations to the constructor in 2d39e46. Had to guard against opts.tableConfig being null, which it is in RFileClientTest.
created_at: 2025-04-23T15:21:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,219 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iteratorsImpl;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      this.env = Optional.of(env);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not scan");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (samplerConfig.isEmpty()) {
path: core/src/main/java/org/apache/accumulo/core/iteratorsImpl/ClientIteratorEnvironment.java
user: login: kevinrr888
body: Should this be checking `sampleEnabled` instead or in addition to or is this the correct check? Maybe this method is saying clone it with sampling enabled, regardless of state of `sampleEnabled` in `this`, so this check might be correct.
created_at: 2025-04-23T19:42:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iteratorsImpl;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      this.env = Optional.of(env);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not scan");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (samplerConfig.isEmpty()) {
+      throw new SampleNotPresentException();
+    }
+    return new ClientIteratorEnvironment(this);
path: core/src/main/java/org/apache/accumulo/core/iteratorsImpl/ClientIteratorEnvironment.java
user: login: kevinrr888
body: Should the returned value have `sampleEnabled` set to true on it? A `sampleEnabled = false` in `this` will persist in the new returned value
created_at: 2025-04-23T19:53:04Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: Moving out of the public for now seems good, can always move it into API later.  Seems like there is potential, but would require some work to figure that out.
created_at: 2025-04-23T20:00:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -295,9 +230,14 @@ public Iterator<Entry<Key,Value>> iterator() {
 
     SortedKeyValueIterator<Key,Value> skvi;
     try {
-      IteratorEnvironment iterEnv = new ClientSideIteratorEnvironment(
-          getSamplerConfiguration() != null, getIteratorSamplerConfigurationInternal());
-
+      ClientIteratorEnvironment.Builder builder = new ClientIteratorEnvironment.Builder()
+          .withClient(context.get()).withAuthorizations(getAuthorizations())
+          .withScope(IteratorScope.scan).withTableId(tableId.get())
+          .withSamplerConfiguration(getIteratorSamplerConfigurationInternal());
+      if (getSamplerConfiguration() != null) {
+        builder.withSamplingEnabled();
+      }
+      IteratorEnvironment iterEnv = builder.build();
path: core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
user: login: kevinrr888
body: I believe this (and maybe elsewhere the Builder is used) needs a `withEnvironment` to keep functionality of `getServiceEnv` and `getPluginEnv`
created_at: 2025-04-23T20:01:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iterators;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    Configuration hadoopConf = new Configuration();
+    FileSystem fs = FileSystem.get(hadoopConf);
+    return new MapFileIterator(fs, mapFileName, hadoopConf);
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not scan");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    String samplerClass =
+        getPluginEnv().getConfiguration(getTableId()).get(Property.TABLE_SAMPLER.getKey());
+    if (samplerClass == null || samplerClass.isBlank()) {
+      throw new SampleNotPresentException();
+    }
path: core/src/main/java/org/apache/accumulo/core/iterators/ClientIteratorEnvironment.java
user: login: keith-turner
body: Opened #5500 to remind myself of the follow on work I want to do here.
created_at: 2025-04-23T20:04:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +78,46 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Configuration conf;
+    private final Configuration tableConf;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      conf = new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+      ConfigurationCopy tableCC = new ConfigurationCopy(DefaultConfiguration.getInstance());
+      if (opts.tableConfig != null) {
+        opts.tableConfig.forEach(tableCC::set);
+      }
+      tableConf = new ConfigurationImpl(tableCC);
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+      return conf;
+    }
+
+    @Override
+    public Configuration getConfiguration(TableId tableId) {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return tableConf;
+    }
+
+  }
+
   private static final byte[] EMPTY_BYTES = new byte[0];
   private static final Range EMPTY_RANGE = new Range();
+  private static final String TABLE_NAME = "rfileScanner";
+  private static final TableId TABLE_ID = TableId.of(TABLE_NAME);
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: I discussed in more detail [here](https://github.com/apache/accumulo/pull/4816#issuecomment-2347164444) but I'm not sure about a dummy table id/name here. Both `null` and a dummy id have their drawbacks though, so may be up to preference.

In my opinion, a `null` table id makes sense since there is no table in this context while still allowing for `env.getPluginEnv().getConfiguration(env.getTableId())` to work (`getConfiguration` just ensures the passed in value is `null`)
created_at: 2025-04-23T20:14:59Z
author_association: MEMBER
diff_hunk: @@ -74,8 +78,46 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Configuration conf;
+    private final Configuration tableConf;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      conf = new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+      ConfigurationCopy tableCC = new ConfigurationCopy(DefaultConfiguration.getInstance());
+      if (opts.tableConfig != null) {
+        opts.tableConfig.forEach(tableCC::set);
+      }
+      tableConf = new ConfigurationImpl(tableCC);
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+      return conf;
+    }
+
+    @Override
+    public Configuration getConfiguration(TableId tableId) {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return tableConf;
+    }
+
+  }
+
   private static final byte[] EMPTY_BYTES = new byte[0];
   private static final Range EMPTY_RANGE = new Range();
+  private static final String TABLE_NAME = "rfileScanner";
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: This should be deleted if `getTableName` is removed
created_at: 2025-04-23T20:16:16Z
author_association: MEMBER
diff_hunk: @@ -74,8 +78,46 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Configuration conf;
+    private final Configuration tableConf;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      conf = new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+      ConfigurationCopy tableCC = new ConfigurationCopy(DefaultConfiguration.getInstance());
+      if (opts.tableConfig != null) {
+        opts.tableConfig.forEach(tableCC::set);
+      }
+      tableConf = new ConfigurationImpl(tableCC);
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: kevinrr888
body: I don't think `getTableName` should be supported here. There is no need for a user to call this. Should probably just throw an exception
created_at: 2025-04-23T20:16:49Z
author_association: MEMBER
diff_hunk: @@ -79,6 +80,30 @@ public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
     this.topLevelIterators = new ArrayList<>();
   }
 
+  public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
path: server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java
user: login: keith-turner
body: What is going on w/ this new constructor?  Its only used in a test, what is preventing the test from using the new builder?
created_at: 2025-04-23T20:25:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,6 +80,30 @@ public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
     this.topLevelIterators = new ArrayList<>();
   }
 
+  public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
path: server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java
user: login: dlmarion
body: which test?
created_at: 2025-04-23T20:35:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,6 +80,30 @@ public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
     this.topLevelIterators = new ArrayList<>();
   }
 
+  public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
path: server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java
user: login: dlmarion
body: ah, I think it's because I was trying to get all of the server side code to use server-side iterator environment objects vs having all the server side tests using the new ClientIteratorEnvironment.
created_at: 2025-04-23T20:38:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -295,9 +230,14 @@ public Iterator<Entry<Key,Value>> iterator() {
 
     SortedKeyValueIterator<Key,Value> skvi;
     try {
-      IteratorEnvironment iterEnv = new ClientSideIteratorEnvironment(
-          getSamplerConfiguration() != null, getIteratorSamplerConfigurationInternal());
-
+      ClientIteratorEnvironment.Builder builder = new ClientIteratorEnvironment.Builder()
+          .withClient(context.get()).withAuthorizations(getAuthorizations())
+          .withScope(IteratorScope.scan).withTableId(tableId.get())
+          .withSamplerConfiguration(getIteratorSamplerConfigurationInternal());
+      if (getSamplerConfiguration() != null) {
+        builder.withSamplingEnabled();
+      }
+      IteratorEnvironment iterEnv = builder.build();
path: core/src/main/java/org/apache/accumulo/core/client/ClientSideIteratorScanner.java
user: login: kevinrr888
body: Nevermind, I think `withClient` covers this
created_at: 2025-04-23T21:04:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.core.iteratorsImpl;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.PluginEnvironment;
+import org.apache.accumulo.core.client.SampleNotPresentException;
+import org.apache.accumulo.core.client.sample.SamplerConfiguration;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.ClientServiceEnvironmentImpl;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.common.ServiceEnvironment;
+
+public class ClientIteratorEnvironment implements IteratorEnvironment {
+
+  public static class Builder {
+
+    private Optional<IteratorScope> scope = Optional.empty();
+    private boolean isFullMajorCompaction = false;
+    private Optional<Authorizations> auths = Optional.empty();
+    private boolean isUserCompaction = false;
+    private Optional<TableId> tableId = Optional.empty();
+    private Optional<SamplerConfiguration> samplerConfig = Optional.empty();
+    private boolean samplingEnabled = false;
+    protected Optional<ClientServiceEnvironmentImpl> env = Optional.empty();
+
+    public Builder withScope(IteratorScope scope) {
+      this.scope = Optional.of(scope);
+      return this;
+    }
+
+    public Builder isFullMajorCompaction() {
+      this.isFullMajorCompaction = true;
+      return this;
+    }
+
+    public Builder withAuthorizations(Authorizations auths) {
+      this.auths = Optional.of(auths);
+      return this;
+    }
+
+    public Builder isUserCompaction() {
+      this.isUserCompaction = true;
+      return this;
+    }
+
+    public Builder withTableId(TableId tableId) {
+      this.tableId = Optional.of(tableId);
+      return this;
+    }
+
+    public Builder withSamplingEnabled() {
+      this.samplingEnabled = true;
+      return this;
+    }
+
+    public Builder withSamplerConfiguration(SamplerConfiguration sc) {
+      this.samplerConfig = Optional.ofNullable(sc);
+      return this;
+    }
+
+    public ClientIteratorEnvironment.Builder withEnvironment(ClientServiceEnvironmentImpl env) {
+      this.env = Optional.of(env);
+      return this;
+    }
+
+    public Builder withClient(AccumuloClient client) {
+      this.env = Optional.of(new ClientServiceEnvironmentImpl((ClientContext) client));
+      return this;
+    }
+
+    public ClientIteratorEnvironment build() {
+      return new ClientIteratorEnvironment(this);
+    }
+
+  }
+
+  public static final IteratorEnvironment DEFAULT = new Builder().build();
+
+  private final Optional<IteratorScope> scope;
+  private final boolean isFullMajorCompaction;
+  private final Optional<Authorizations> auths;
+  private final boolean isUserCompaction;
+  private final Optional<TableId> tableId;
+  private final Optional<SamplerConfiguration> samplerConfig;
+  private final boolean samplingEnabled;
+  private final Optional<ClientServiceEnvironmentImpl> env;
+
+  private ClientIteratorEnvironment(Builder builder) {
+    this.scope = builder.scope;
+    this.isFullMajorCompaction = builder.isFullMajorCompaction;
+    this.auths = builder.auths;
+    this.isUserCompaction = builder.isUserCompaction;
+    this.tableId = builder.tableId;
+    this.samplerConfig = builder.samplerConfig;
+    this.env = builder.env;
+    this.samplingEnabled = builder.samplingEnabled;
+  }
+
+  /**
+   * Copy constructor used for enabling sample. Only called from {@link cloneWithSamplingEnabled}.
+   */
+  private ClientIteratorEnvironment(ClientIteratorEnvironment copy) {
+    this.scope = copy.scope;
+    this.isFullMajorCompaction = copy.isFullMajorCompaction;
+    this.auths = copy.auths;
+    this.isUserCompaction = copy.isUserCompaction;
+    this.tableId = copy.tableId;
+    this.samplerConfig = copy.samplerConfig;
+    this.env = copy.env;
+    this.samplingEnabled = true;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
+      throws IOException {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public IteratorScope getIteratorScope() {
+    return scope.orElseThrow();
+  }
+
+  @Override
+  public boolean isFullMajorCompaction() {
+    if (getIteratorScope() != IteratorScope.majc) {
+      throw new IllegalStateException("Iterator scope is not majc");
+    }
+    return isFullMajorCompaction;
+  }
+
+  @Override
+  @Deprecated(since = "2.0.0")
+  public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+    throw new UnsupportedOperationException("Feature not supported");
+  }
+
+  @Override
+  public Authorizations getAuthorizations() {
+    if (getIteratorScope() != IteratorScope.scan) {
+      throw new IllegalStateException("Iterator scope is not scan");
+    }
+    return auths.orElseThrow();
+  }
+
+  @Override
+  public IteratorEnvironment cloneWithSamplingEnabled() {
+    if (samplerConfig.isEmpty()) {
+      throw new SampleNotPresentException();
+    }
+    return new ClientIteratorEnvironment(this);
path: core/src/main/java/org/apache/accumulo/core/iteratorsImpl/ClientIteratorEnvironment.java
user: login: dlmarion
body: The copy constructor sets samplingEnabled to true.
created_at: 2025-04-23T21:10:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,6 +80,30 @@ public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
     this.topLevelIterators = new ArrayList<>();
   }
 
+  public TabletIteratorEnvironment(ServerContext context, IteratorScope scope,
path: server/base/src/main/java/org/apache/accumulo/server/iterators/TabletIteratorEnvironment.java
user: login: keith-turner
body: I experimented w/ using the new code in InMemoryMapTest to create an iterenv and it worked really nicely and the test passed.   The new class is a good general purpose builder for an IteratorEnv, seems fine to me to use it in the server code and remove this new constructor.

```java
      IteratorEnvironment iterEnv =
          new ClientIteratorEnvironment.Builder().withClient(getServerContext())
              .withScope(IteratorScope.scan).withTableId(TableId.of("foo")).build();
      IteratorEnvironment iterEnvSC =
          new ClientIteratorEnvironment.Builder().withClient(getServerContext())
              .withScope(IteratorScope.scan).withTableId(TableId.of("foo")).withSamplingEnabled()
              .withSamplerConfiguration(sampleConfig.toSamplerConfiguration()).build();
      SortedKeyValueIterator<Key,Value> iter0dc1 = iter0.deepCopy(iterEnv);
      SortedKeyValueIterator<Key,Value> iter0dc2 = iter0.deepCopy(iterEnvSC);
      SortedKeyValueIterator<Key,Value> iter1dc1 = iter1.deepCopy(iterEnv);
      SortedKeyValueIterator<Key,Value> iter1dc2 = iter1.deepCopy(iterEnvSC);
      SortedKeyValueIterator<Key,Value> iter2dc1 = iter2.deepCopy(iterEnv);
      SortedKeyValueIterator<Key,Value> iter2dc2 = iter2.deepCopy(iterEnvSC);
```

Not a change for this PR, but maybe in the public API I  we cold eventually add a static `builder()` method to `IteratorEnvironment` and use this new code as the impl.   Then could write the above code like the following.

```java
      IteratorEnvironment iterEnv =
          IteratorEnvironment.builder().withClient(getServerContext())
              .withScope(IteratorScope.scan).withTableId(TableId.of("foo")).build();
```

In this PR would could add a static `builder()` method to ClientIteratorEnvironment. Would make the code that uses the builder slightly shorter. 
created_at: 2025-04-24T14:11:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,8 +78,46 @@
 
 class RFileScanner extends ScannerOptions implements Scanner {
 
+  private static class RFileScannerEnvironmentImpl extends ClientServiceEnvironmentImpl {
+
+    private final Configuration conf;
+    private final Configuration tableConf;
+
+    public RFileScannerEnvironmentImpl(Opts opts) {
+      super(null);
+      conf = new ConfigurationImpl(new ConfigurationCopy(DefaultConfiguration.getInstance()));
+      ConfigurationCopy tableCC = new ConfigurationCopy(DefaultConfiguration.getInstance());
+      if (opts.tableConfig != null) {
+        opts.tableConfig.forEach(tableCC::set);
+      }
+      tableConf = new ConfigurationImpl(tableCC);
+    }
+
+    @Override
+    public String getTableName(TableId tableId) throws TableNotFoundException {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return TABLE_NAME;
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+      return conf;
+    }
+
+    @Override
+    public Configuration getConfiguration(TableId tableId) {
+      Preconditions.checkArgument(tableId == TABLE_ID, "Expected " + TABLE_ID + " obtained"
+          + " from IteratorEnvironment.getTableId(), but got: " + tableId);
+      return tableConf;
+    }
+
+  }
+
   private static final byte[] EMPTY_BYTES = new byte[0];
   private static final Range EMPTY_RANGE = new Range();
+  private static final String TABLE_NAME = "rfileScanner";
+  private static final TableId TABLE_ID = TableId.of(TABLE_NAME);
path: core/src/main/java/org/apache/accumulo/core/client/rfile/RFileScanner.java
user: login: keith-turner
body: Could spin this off in a separate issue since it was a preexisting pattern in the code. 
created_at: 2025-04-24T14:14:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,56 +0,0 @@
-/*
- * 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
- *
- *   https://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.accumulo.core.iterators;
-
-import java.io.IOException;
-
-import org.apache.accumulo.core.conf.AccumuloConfiguration;
-import org.apache.accumulo.core.conf.DefaultConfiguration;
-import org.apache.accumulo.core.data.Key;
-import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.FileSystem;
-
-public class DefaultIteratorEnvironment implements IteratorEnvironment {
-
-  AccumuloConfiguration conf;
-  Configuration hadoopConf = new Configuration();
-
-  public DefaultIteratorEnvironment(AccumuloConfiguration conf) {
-    this.conf = conf;
-  }
-
-  public DefaultIteratorEnvironment() {
-    this.conf = DefaultConfiguration.getInstance();
-  }
-
-  @Deprecated(since = "2.0.0")
-  @Override
-  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
-      throws IOException {
-    FileSystem fs = FileSystem.get(hadoopConf);
-    return new MapFileIterator(fs, mapFileName, hadoopConf);
-  }
path: core/src/test/java/org/apache/accumulo/core/iterators/DefaultIteratorEnvironment.java
user: login: kevinrr888
body: I may be missing something, but seems like the replacement `public static final IteratorEnvironment DEFAULT = new Builder().build();` for this class will `throw new UnsupportedOperationException("Feature not supported");` for this method. If DEFAULT is only used in testing this probably doesn't matter so long as everything still passes
created_at: 2025-04-24T15:24:00Z
author_association: MEMBER
diff_hunk: @@ -1,56 +0,0 @@
-/*
- * 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
- *
- *   https://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.accumulo.core.iterators;
-
-import java.io.IOException;
-
-import org.apache.accumulo.core.conf.AccumuloConfiguration;
-import org.apache.accumulo.core.conf.DefaultConfiguration;
-import org.apache.accumulo.core.data.Key;
-import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.iteratorsImpl.system.MapFileIterator;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.FileSystem;
-
-public class DefaultIteratorEnvironment implements IteratorEnvironment {
-
-  AccumuloConfiguration conf;
-  Configuration hadoopConf = new Configuration();
-
-  public DefaultIteratorEnvironment(AccumuloConfiguration conf) {
-    this.conf = conf;
-  }
-
-  public DefaultIteratorEnvironment() {
-    this.conf = DefaultConfiguration.getInstance();
-  }
-
-  @Deprecated(since = "2.0.0")
-  @Override
-  public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName)
-      throws IOException {
-    FileSystem fs = FileSystem.get(hadoopConf);
-    return new MapFileIterator(fs, mapFileName, hadoopConf);
-  }
path: core/src/test/java/org/apache/accumulo/core/iterators/DefaultIteratorEnvironment.java
user: login: dlmarion
body: The ITs passed with this change, so I think we are safe. IIRC, Keith said above that he could not find any uses of it.
created_at: 2025-04-24T17:37:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: dlmarion
created_at: 2025-04-18T21:28:03Z
body: The replacement of the `IteratorEnvironment` implementations in `ClientSideIteratorScanner`, `RFileScanner`, `OfflineIterator` with `ClientIteratorEnvironment` need to be scrutinized. I know that in one case one of the implementations handled the `registerSideChannel` method, but I could not find it being used anywhere so I just deleted it.
author_association: CONTRIBUTOR
user: login: keith-turner
created_at: 2025-04-22T22:02:38Z
body: > Overall, I think these changes are a good idea, however, this refactoring can very easily have bugs that are hard to spot. I'm not sure if this is the best for a 2.1 change, risking potentially introducing more bugs than the original PR set out to fix.

@kevinrr888 I also like these changes and was a bit nervous about making the changes in 2.1.  Personally I would like to try to get these changes in (because they make things more consistent which will reduce bugs long term, also the current inconsistency feels really buggy) and review our test coverage around this code.  While reviewing this today I found a place I would like to add a unit test for RFileScanner.  More generally we may want to eventually have something like #5474 that covers the iteratorEnv in all the different scanner types (like RFileScanner,OfflineScanner,ScannerImpl,BatchScanner,ClientSideIteratorScanner).  We could try to as much as possible to run the same tests across all the different scanner types.   For this PR I will try to run all the unit test w/ code coverage and see what the coverage looks like for the new code.
author_association: CONTRIBUTOR
user: login: kevinrr888
created_at: 2025-04-23T19:01:06Z
body: > While reviewing this today I found a place I would like to add a unit test for RFileScanner. More generally we may want to eventually have something like https://github.com/apache/accumulo/pull/5474 that covers the iteratorEnv in all the different scanner types (like RFileScanner,OfflineScanner,ScannerImpl,BatchScanner,ClientSideIteratorScanner). We could try to as much as possible to run the same tests across all the different scanner types. For this PR I will try to run all the unit test w/ code coverage and see what the coverage looks like for the new code.

@keith-turner - In #4816, I changed the `IteratorEnvIT` to test across different scanner types. Could use that code and expand on that as needed for full test coverage. Could potentially move those changes into this PR, or change that PR to only have the changes not accomplished by this PR and merge them both in.
author_association: MEMBER
user: login: keith-turner
created_at: 2025-04-23T19:10:18Z
body: > @keith-turner - In https://github.com/apache/accumulo/pull/4816, I changed the IteratorEnvIT to test across different scanner types. Could use that code and expand on that as needed for full test coverage. Could potentially move those changes into this PR, or change that PR to only have the changes not accomplished by this PR and merge them both in.

After this PR is merged, seems like it would good to circle back to #4816 and pull the useful bits into another PR.
author_association: CONTRIBUTOR
user: login: dlmarion
created_at: 2025-04-23T20:10:37Z
body: I'm not sure about the change I made in 1c75b6c. With the builder pattern should we allow the user to call whatever methods they want in which-ever order they want?
author_association: CONTRIBUTOR
user: login: keith-turner
created_at: 2025-04-23T20:35:38Z
body: > I'm not sure about the change I made in https://github.com/apache/accumulo/commit/1c75b6c646bbd4bf5d7cdfff7d387445ff636445. With the builder pattern should we allow the user to call whatever methods they want in which-ever order they want?

That change is nice for runtime validation.    Could add more runtime checks making sure things are empty when set in the builder w/ the assumption that if the same builder method is called twice it probably is not what was intended.  I like the way it currently is, the optionals provide a lot of runtime validation (like for the case attempting to use something not set in the builder).
author_association: CONTRIBUTOR
user: login: dlmarion
created_at: 2025-04-23T20:40:59Z
body: 
> That change is nice for runtime validation. Could add more runtime checks making sure things are empty when set in the builder w/ the assumption that if the same builder method is called twice it probably is not what was intended. I like the way it currently is, the optionals provide a lot of runtime validation (like for the case attempting to use something not set in the builder).

Ok, I can add more validation on the Builder

author_association: CONTRIBUTOR
user: login: dlmarion
created_at: 2025-04-24T12:03:34Z
body: Full IT build passed
author_association: CONTRIBUTOR
user: login: dlmarion
created_at: 2025-04-24T17:39:19Z
body: @keith-turner @kevinrr888 - Thanks for all your reviews. I'm going to work on merging this in and up to main, then I'll create the follow-on issues.
author_association: CONTRIBUTOR