diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java
index e14b13d96a0d9781512a2b5415a9e9bf8e4aade5..f53ebde58f6ae7e9a6228ae3c6f262a559dfba82 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/PredicateExecutor.java
@@ -16,10 +16,12 @@
 
 package ch.systemsx.cisd.openbis.generic.server.authorization;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.hibernate.Query;
 import org.hibernate.Session;
@@ -47,7 +49,10 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.GroupPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.QueryPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleAccessPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
+import ch.systemsx.cisd.openbis.plugin.generic.server.batch.BatchOperationExecutor;
+import ch.systemsx.cisd.openbis.plugin.generic.server.batch.IBatchOperation;
 
 /**
  * A static class to execute {@link IPredicate}.
@@ -255,54 +260,99 @@ public final class PredicateExecutor
             }
         }
 
-        @SuppressWarnings("unchecked")
         public DataSetAccessPE tryGetDatasetAccessData(String dataSetCode)
         {
-            Session sess = daoFactory.getSessionFactory().getCurrentSession();
-            Query query = sess.getNamedQuery(DataSetAccessPE.DATASET_ACCESS_QUERY_NAME);
-            query = query.setReadOnly(true);
-            String[] codes =
-                { dataSetCode };
-            List<DataSetAccessPE> results =
-                    query.setParameterList(DataSetAccessPE.DATA_SET_CODES_PARAMETER_NAME, codes)
-                            .list();
+            Set<DataSetAccessPE> results =
+                    getDatasetCollectionAccessData(Arrays.asList(dataSetCode));
             if (results.size() < 1)
                 return null;
-            return results.get(0);
+            return results.iterator().next();
         }
 
-        @SuppressWarnings("unchecked")
-        public List<DataSetAccessPE> tryGetDatasetCollectionAccessData(List<String> dataSetCodes)
+        public Set<DataSetAccessPE> getDatasetCollectionAccessData(final List<String> dataSetCodes)
         {
 
             Session sess = daoFactory.getSessionFactory().getCurrentSession();
-            Query query = sess.getNamedQuery(DataSetAccessPE.DATASET_ACCESS_QUERY_NAME);
-            query = query.setReadOnly(true);
+            final Query query = sess.getNamedQuery(DataSetAccessPE.DATASET_ACCESS_QUERY_NAME);
+            query.setReadOnly(true);
 
             // WORKAROUND Problem in Hibernate when the number of data set codes > 1000
             // Though this query runs quickly within the pgadmin tool, even for large numbers of
             // data set codes, Hibernate becomes *very* slow when the size of the data set codes
             // exceeds 1000. For that reason, break down the query into smaller sections and
             // reassemble the results.
-            ArrayList<DataSetAccessPE> fullResults = new ArrayList<DataSetAccessPE>();
+            final Set<DataSetAccessPE> fullResults = new HashSet<DataSetAccessPE>();
 
-            int size = dataSetCodes.size();
-            // blockSize is a magic number -- I don't know what the optimal value is, but execution
-            // speed slows down if the blockSize > 1999
-            int blockSize = 999;
-            int start, end;
-
-            // Loop over the codes, one block at a time
-            for (start = 0, end = Math.min(start + blockSize, size); start < size; start = end, end =
-                    Math.min(start + blockSize, size))
-            {
-                List<String> sublist = dataSetCodes.subList(start, end);
-                query =
+            BatchOperationExecutor.executeInBatches(new IBatchOperation<String>()
+                {
+                    public void execute(List<String> entities)
+                    {
                         query.setParameterList(DataSetAccessPE.DATA_SET_CODES_PARAMETER_NAME,
-                                sublist);
-                List<DataSetAccessPE> results = query.list();
-                fullResults.addAll(results);
-            }
+                                entities);
+                        List<DataSetAccessPE> results = cast(query.list());
+                        fullResults.addAll(results);
+                    }
+
+                    public List<String> getAllEntities()
+                    {
+                        return dataSetCodes;
+                    }
+
+                    public String getEntityName()
+                    {
+                        return "dataset";
+                    }
+
+                    public String getOperationName()
+                    {
+                        return "authorization";
+                    }
+                });
+
+            return fullResults;
+        }
+
+        public Set<SampleAccessPE> getSampleCollectionAccessData(final List<TechId> sampleTechIds)
+        {
+            Session sess = daoFactory.getSessionFactory().getCurrentSession();
+            final Query querySpaceSamples =
+                    sess.getNamedQuery(SampleAccessPE.SPACE_SAMPLE_ACCESS_QUERY_NAME);
+            querySpaceSamples.setReadOnly(true);
+            final Query querySharedSamples =
+                    sess.getNamedQuery(SampleAccessPE.SHARED_SAMPLE_ACCESS_QUERY_NAME);
+            querySharedSamples.setReadOnly(true);
+
+            final Set<SampleAccessPE> fullResults = new HashSet<SampleAccessPE>();
+
+            BatchOperationExecutor.executeInBatches(new IBatchOperation<TechId>()
+                {
+                    public void execute(List<TechId> entities)
+                    {
+                        querySpaceSamples.setParameterList(
+                                SampleAccessPE.SAMPLE_IDS_PARAMETER_NAME, TechId.asLongs(entities));
+                        querySharedSamples.setParameterList(
+                                SampleAccessPE.SAMPLE_IDS_PARAMETER_NAME, TechId.asLongs(entities));
+                        List<SampleAccessPE> spaceSamples = cast(querySpaceSamples.list());
+                        List<SampleAccessPE> sharedSamples = cast(querySharedSamples.list());
+                        fullResults.addAll(spaceSamples);
+                        fullResults.addAll(sharedSamples);
+                    }
+
+                    public List<TechId> getAllEntities()
+                    {
+                        return sampleTechIds;
+                    }
+
+                    public String getEntityName()
+                    {
+                        return "sample";
+                    }
+
+                    public String getOperationName()
+                    {
+                        return "authorization";
+                    }
+                });
 
             return fullResults;
         }
@@ -352,5 +402,18 @@ public final class PredicateExecutor
             return daoFactory.getQueryDAO().getByTechId(techId);
         }
 
+        /**
+         * Casts given <var>list</var> to specified type.
+         * <p>
+         * The purpose of this method is to avoid <code>SuppressWarnings("unchecked")</code> in
+         * calling methods.
+         * </p>
+         */
+        @SuppressWarnings("unchecked")
+        private static final <T> List<T> cast(final List list)
+        {
+            return list;
+        }
+
     }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
index 3689e98855bf8d7ecddf30d0dd14b22d65a6efec..14b23da2346cdc8e7fe3ac85b6c3b8fecdfe027a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
@@ -31,6 +31,7 @@ import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.DataSetCo
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.DataSetCodePredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSampleCriteriaPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ProjectUpdatesPredicate;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleTechIdCollectionPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SampleTechIdPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.SpaceIdentifierPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.AbstractExpressionPredicate.DeleteGridCustomColumnPredicate;
@@ -643,8 +644,9 @@ public interface ICommonServer extends IServer
     @Transactional
     @RolesAllowed(RoleSet.POWER_USER)
     @DatabaseCreateOrDeleteModification(value = ObjectKind.SAMPLE)
-    public void deleteSamples(String sessionToken,
-            @AuthorizationGuard(guardClass = SampleTechIdPredicate.class) List<TechId> sampleIds,
+    public void deleteSamples(
+            String sessionToken,
+            @AuthorizationGuard(guardClass = SampleTechIdCollectionPredicate.class) List<TechId> sampleIds,
             String reason);
 
     /**
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java
index 66de4b37e1b45ce331e97317a4d794227addb66b..f0ca31bd9b4deb2b5d6a47a58ed1bd92aa402669 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationDataProvider.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.shared.authorization;
 
 import java.util.List;
+import java.util.Set;
 
 import ch.systemsx.cisd.openbis.generic.shared.IDatabaseInstanceFinder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.PermId;
@@ -27,6 +28,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.GridCustomFilterPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.GroupPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.QueryPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleAccessPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 
 /**
@@ -56,7 +58,12 @@ public interface IAuthorizationDataProvider extends IDatabaseInstanceFinder
     /**
      * Returns the information necessary to determine if a user is allowed to access the data sets.
      */
-    public List<DataSetAccessPE> tryGetDatasetCollectionAccessData(List<String> dataSetCodes);
+    public Set<DataSetAccessPE> getDatasetCollectionAccessData(List<String> dataSetCodes);
+
+    /**
+     * Returns the information necessary to determine if a user is allowed to access the samples.
+     */
+    public Set<SampleAccessPE> getSampleCollectionAccessData(List<TechId> sampleIds);
 
     /**
      * Returns the group of an entity with given <var>entityKind</var> and <var>techId</var>
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DataSetCodeCollectionPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DataSetCodeCollectionPredicate.java
index a171ef03edbcde5c4305b11b944c443a363e8f6a..28e193c23e5c60f42eaef286b7bf9e9cbe424a45 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DataSetCodeCollectionPredicate.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DataSetCodeCollectionPredicate.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.shared.authorization.predicate;
 
 import java.util.List;
+import java.util.Set;
 
 import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.RoleWithIdentifier;
@@ -44,13 +45,8 @@ public class DataSetCodeCollectionPredicate extends AbstractGroupPredicate<List<
     {
         assert initialized : "Predicate has not been initialized";
 
-        List<DataSetAccessPE> accessData =
-                authorizationDataProvider.tryGetDatasetCollectionAccessData(dataSetCodes);
-
-        if (accessData == null)
-        {
-            return Status.OK;
-        }
+        Set<DataSetAccessPE> accessData =
+                authorizationDataProvider.getDatasetCollectionAccessData(dataSetCodes);
 
         for (DataSetAccessPE accessDatum : accessData)
         {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DelegatedPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DelegatedPredicate.java
index 5cb8e4f230add9acfde9417ac101b5b3ab4aee4e..61181f2605084763493daa2be3c9a7cfaef9f8b2 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DelegatedPredicate.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/DelegatedPredicate.java
@@ -39,6 +39,8 @@ public abstract class DelegatedPredicate<P, T> extends AbstractPredicate<T>
 {
     private final IPredicate<P> delegate;
 
+    protected IAuthorizationDataProvider authorizationDataProvider;
+
     public DelegatedPredicate(final IPredicate<P> delegate)
     {
         this.delegate = delegate;
@@ -55,6 +57,7 @@ public abstract class DelegatedPredicate<P, T> extends AbstractPredicate<T>
 
     public final void init(IAuthorizationDataProvider provider)
     {
+        this.authorizationDataProvider = provider;
         delegate.init(provider);
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SampleTechIdCollectionPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SampleTechIdCollectionPredicate.java
new file mode 100644
index 0000000000000000000000000000000000000000..3852ff440af04f6d875ba9420e1c48b322d2e2a6
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/SampleTechIdCollectionPredicate.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2009 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.authorization.predicate;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.ShouldFlattenCollections;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleAccessPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.DatabaseInstanceIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleOwnerIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
+
+/**
+ * An <code>IPredicate</code> implementation based on a list of sample {@link TechId}s.
+ * 
+ * @author Piotr Buczek
+ */
+@ShouldFlattenCollections(value = false)
+public class SampleTechIdCollectionPredicate extends
+        DelegatedPredicate<List<SampleOwnerIdentifier>, List<TechId>>
+{
+
+    public SampleTechIdCollectionPredicate()
+    {
+        super(new SampleOwnerIdentifierCollectionPredicate(true));
+    }
+
+    @Override
+    public List<SampleOwnerIdentifier> convert(List<TechId> techIds)
+    {
+        ArrayList<SampleOwnerIdentifier> ownerIds = new ArrayList<SampleOwnerIdentifier>();
+
+        Set<SampleAccessPE> accessData =
+                authorizationDataProvider.getSampleCollectionAccessData(techIds);
+
+        for (SampleAccessPE accessDatum : accessData)
+        {
+            String groupCode = accessDatum.getGroupCode();
+            String dbInstanceCode = accessDatum.getDatabaseInstanceCode();
+            if (groupCode != null)
+            {
+                ownerIds.add(new SampleOwnerIdentifier(new SpaceIdentifier(
+                        DatabaseInstanceIdentifier.createHome(), groupCode)));
+            } else
+            {
+                ownerIds.add(new SampleOwnerIdentifier(new DatabaseInstanceIdentifier(
+                        dbInstanceCode)));
+            }
+        }
+
+        return ownerIds;
+    }
+
+    @Override
+    public final String getCandidateDescription()
+    {
+        return "sample technical ids"; 
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/TechId.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/TechId.java
index a0a130b4d1e81f3933c57085cdc366abda6f2038..2e174712b2e495f2b66e1828634714205b62a61e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/TechId.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/TechId.java
@@ -88,6 +88,21 @@ public class TechId implements IIdHolder, IsSerializable, Serializable
         return results;
     }
 
+    /**
+     * Convenience method for getting a list of long ids from given list of {@link TechId}s.
+     * 
+     * @see #create(IIdHolder)
+     */
+    public static List<Long> asLongs(List<TechId> techIds)
+    {
+        List<Long> results = new ArrayList<Long>();
+        for (TechId techId : techIds)
+        {
+            results.add(techId.getId());
+        }
+        return results;
+    }
+
     public Long getId()
     {
         return id;
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSetAccessPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSetAccessPE.java
index 479ca131da1f4ba45c874de2adfc0b7ea6f58444..9b00a972cd40110adf3a1ed0471427af9b7d8694 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSetAccessPE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSetAccessPE.java
@@ -22,6 +22,9 @@ import javax.persistence.Id;
 import javax.persistence.NamedNativeQuery;
 import javax.persistence.SqlResultSetMapping;
 
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
 /**
  * A PE for retrieving only the information necessary to determine if a user/person can access a
  * data set.
@@ -94,4 +97,37 @@ public class DataSetAccessPE
     {
         return databaseInstanceCode;
     }
+
+    //
+    // Object
+    //
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+        if (obj instanceof DataSetAccessPE == false)
+        {
+            return false;
+        }
+        final DataSetAccessPE that = (DataSetAccessPE) obj;
+        final EqualsBuilder builder = new EqualsBuilder();
+        builder.append(getGroupCode(), that.getGroupCode());
+        builder.append(getDatabaseInstanceCode(), that.getDatabaseInstanceCode());
+        builder.append(getDatabaseInstanceUuid(), that.getDatabaseInstanceUuid());
+        return builder.isEquals();
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final HashCodeBuilder builder = new HashCodeBuilder();
+        builder.append(getGroupCode());
+        builder.append(getDatabaseInstanceCode());
+        builder.append(getDatabaseInstanceUuid());
+        return builder.toHashCode();
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SampleAccessPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SampleAccessPE.java
new file mode 100644
index 0000000000000000000000000000000000000000..cde0b2e324c72d973ce75989921ea15406eb71f0
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SampleAccessPE.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.dto;
+
+import javax.persistence.Entity;
+import javax.persistence.EntityResult;
+import javax.persistence.Id;
+import javax.persistence.NamedNativeQueries;
+import javax.persistence.NamedNativeQuery;
+import javax.persistence.SqlResultSetMapping;
+import javax.persistence.SqlResultSetMappings;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+/**
+ * A PE for retrieving only the information necessary to determine if a user/person can access a
+ * sample.
+ * 
+ * @author Piotr Buczek
+ */
+@Entity
+@SqlResultSetMappings(value =
+    {
+            @SqlResultSetMapping(name = "implicit1", entities = @EntityResult(entityClass = SampleAccessPE.class)),
+            @SqlResultSetMapping(name = "implicit2", entities = @EntityResult(entityClass = SampleAccessPE.class))
+
+    })
+@NamedNativeQueries(value =
+    {
+            @NamedNativeQuery(name = "space_sample_access", query = "SELECT DISTINCT g.code as groupCode, null as databaseInstanceCode "
+                    + "FROM "
+                    + TableNames.SAMPLES_TABLE
+                    + " s, "
+                    + TableNames.GROUPS_TABLE
+                    + " g "
+                    + "WHERE s.id in (:ids) and s.grou_id = g.id", resultSetMapping = "implicit1"),
+            @NamedNativeQuery(name = "shared_sample_access", query = "SELECT DISTINCT dbi.code as databaseInstanceCode, null as groupCode "
+                    + "FROM "
+                    + TableNames.SAMPLES_TABLE
+                    + " s, "
+                    + TableNames.DATABASE_INSTANCES_TABLE
+                    + " dbi "
+                    + "WHERE s.id in (:ids) and s.dbin_id = dbi.id", resultSetMapping = "implicit2")
+
+    })
+public class SampleAccessPE
+{
+    private String groupCode;
+
+    private String databaseInstanceCode;
+
+    public final static String SPACE_SAMPLE_ACCESS_QUERY_NAME = "space_sample_access";
+
+    public final static String SHARED_SAMPLE_ACCESS_QUERY_NAME = "shared_sample_access";
+
+    public final static String SAMPLE_IDS_PARAMETER_NAME = "ids";
+
+    /**
+     * A factory method that should only be used for testing.
+     */
+    public static SampleAccessPE createSampleAccessPEForTest(String dataSetId, String dataSetCode,
+            String groupCode, String databaseInstanceCode)
+    {
+        SampleAccessPE newMe = new SampleAccessPE();
+        newMe.setGroupCode(groupCode);
+        newMe.setDatabaseInstanceCode(databaseInstanceCode);
+        return newMe;
+    }
+
+    void setGroupCode(String groupCode)
+    {
+        this.groupCode = groupCode;
+    }
+
+    void setDatabaseInstanceCode(String databaseInstanceCode)
+    {
+        this.databaseInstanceCode = databaseInstanceCode;
+    }
+
+    @Id
+    public String getGroupCode()
+    {
+        return groupCode;
+    }
+
+    public String getDatabaseInstanceCode()
+    {
+        return databaseInstanceCode;
+    }
+
+    //
+    // Object
+    //
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+        if (obj instanceof SampleAccessPE == false)
+        {
+            return false;
+        }
+        final SampleAccessPE that = (SampleAccessPE) obj;
+        final EqualsBuilder builder = new EqualsBuilder();
+        builder.append(getGroupCode(), that.getGroupCode());
+        builder.append(getDatabaseInstanceCode(), that.getDatabaseInstanceCode());
+        return builder.isEquals();
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final HashCodeBuilder builder = new HashCodeBuilder();
+        builder.append(getGroupCode());
+        builder.append(getDatabaseInstanceCode());
+        return builder.toHashCode();
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericDataSetTypeSlaveServerPlugin.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericDataSetTypeSlaveServerPlugin.java
index 97df9049ff7a94f893c63ef0b5fcb5b81693845d..198be640dc51ab38c7a9ca8e43c5d265f6080d7c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericDataSetTypeSlaveServerPlugin.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericDataSetTypeSlaveServerPlugin.java
@@ -59,8 +59,8 @@ public class GenericDataSetTypeSlaveServerPlugin implements IDataSetTypeSlaveSer
         assert session != null : "Unspecified session.";
         assert newDataSets != null && newDataSets.size() > 0 : "Unspecified data set or empty data sets.";
 
-        new BatchOperationExecutor<NewDataSet>().executeInBatches(new DataSetBatchUpdate(
-                businessObjectFactory.createExternalDataTable(session), newDataSets));
+        BatchOperationExecutor.executeInBatches(new DataSetBatchUpdate(businessObjectFactory
+                .createExternalDataTable(session), newDataSets));
     }
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericSampleTypeSlaveServerPlugin.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericSampleTypeSlaveServerPlugin.java
index ccd7eb52b9df7adb14f76e7bdd27e9f981584a9f..c4967d6d1c9c8ba3f198cd0877c1ef293e081980 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericSampleTypeSlaveServerPlugin.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericSampleTypeSlaveServerPlugin.java
@@ -79,8 +79,8 @@ public final class GenericSampleTypeSlaveServerPlugin implements ISampleTypeSlav
         assert session != null : "Unspecified session.";
         assert newSamples != null && newSamples.size() > 0 : "Unspecified sample or empty samples.";
 
-        new BatchOperationExecutor<NewSample>().executeInBatches(new SampleBatchRegistration(
-                businessObjectFactory.createSampleTable(session), newSamples));
+        BatchOperationExecutor.executeInBatches(new SampleBatchRegistration(businessObjectFactory
+                .createSampleTable(session), newSamples));
     }
 
     public void updateSamples(Session session, List<SampleBatchUpdatesDTO> updateSamples)
@@ -88,7 +88,7 @@ public final class GenericSampleTypeSlaveServerPlugin implements ISampleTypeSlav
         assert session != null : "Unspecified session.";
         assert updateSamples != null && updateSamples.size() > 0 : "Unspecified sample or empty samples.";
 
-        new BatchOperationExecutor<SampleBatchUpdatesDTO>().executeInBatches(new SampleBatchUpdate(
-                businessObjectFactory.createSampleTable(session), updateSamples));
+        BatchOperationExecutor.executeInBatches(new SampleBatchUpdate(businessObjectFactory
+                .createSampleTable(session), updateSamples));
     }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
index c20671abbbf0e67ffd1dc22b032c4772fdfa535e..6e6084bc7f6bbde252771305d4e1eaa922c389e1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/GenericServer.java
@@ -274,10 +274,9 @@ public final class GenericServer extends AbstractServer<IGenericServer> implemen
                 registerSamples(session, samples);
             } else
             {
-                new BatchOperationExecutor<NewSample>()
-                        .executeInBatches(new SampleBatchRegisterOrUpdate(businessObjectFactory
-                                .createSampleLister(session), samples.getNewSamples(), samples
-                                .getSampleType(), session));
+                BatchOperationExecutor.executeInBatches(new SampleBatchRegisterOrUpdate(
+                        businessObjectFactory.createSampleLister(session), samples.getNewSamples(),
+                        samples.getSampleType(), session));
             }
         }
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/batch/BatchOperationExecutor.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/batch/BatchOperationExecutor.java
index dee516faddee20382b59f5944ab041cbad0de8fa..3d50e443f041f84d864c2d0aeadfddc00a203554 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/batch/BatchOperationExecutor.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/server/batch/BatchOperationExecutor.java
@@ -12,33 +12,34 @@ import ch.systemsx.cisd.common.logging.LogFactory;
  * 
  * @author Izabela Adamczyk
  */
-public class BatchOperationExecutor<S>
+public class BatchOperationExecutor
 {
     private static final Logger operationLog =
             LogFactory.getLogger(LogCategory.OPERATION, BatchOperationExecutor.class);
 
     private static final int DEFAULT_BATCH_SIZE = 1000;
 
-    public void executeInBatches(IBatchOperation<S> strategy)
+    public static <S> void executeInBatches(IBatchOperation<S> strategy)
     {
         executeInBatches(strategy, DEFAULT_BATCH_SIZE);
     }
 
-    public void executeInBatches(IBatchOperation<S> strategy, int batchSize)
+    public static <S> void executeInBatches(IBatchOperation<S> strategy, int batchSize)
     {
         assert strategy != null : "Unspecified operation.";
 
         final List<S> allEntities = strategy.getAllEntities();
-        int startIndex = 0;
         int maxIndex = allEntities.size();
-        while (startIndex < maxIndex)
+
+        // Loop over the list, one block at a time
+        for (int startIndex = 0, endIndex = Math.min(startIndex + batchSize, maxIndex); startIndex < maxIndex; startIndex =
+                endIndex, endIndex = Math.min(startIndex + batchSize, maxIndex))
         {
-            final int endIndex = Math.min(startIndex + batchSize, maxIndex);
             final List<S> batch = allEntities.subList(startIndex, endIndex);
             strategy.execute(batch);
             operationLog.info(String.format("%s %s progress: %d/%d", strategy.getEntityName(),
                     strategy.getOperationName(), endIndex, maxIndex));
-            startIndex += batchSize;
         }
     }
+
 }
\ No newline at end of file