diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
index b78370f8ff3c195505e5c6b7788af7d698741c74..2fd82194dd53aaa0bb17edd5774f5bb192dab2d9 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
@@ -732,10 +732,15 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
             throw new UserFailureException("It is not allowed to change the user from remote host "
                     + remoteHost);
         }
-        PersonPE person = daoFactory.getPersonDAO().tryFindPersonByUserId(userID);
+        injectPerson(session, userID);
+    }
+
+    protected void injectPerson(Session session, String personID)
+    {
+        PersonPE person = daoFactory.getPersonDAO().tryFindPersonByUserId(personID);
         if (person == null)
         {
-            throw new UserFailureException("Unknown user: " + userID);
+            throw new UserFailureException("Unknown user: " + personID);
         }
         HibernateUtils.initialize(person.getAllPersonRoles());
         session.setPerson(person);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
index bc496b785cc7f4cee893c87dbac4f8324a9a7deb..6432556f4ad93d7b9ec4cec718c04b45d82bd234 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
@@ -27,8 +27,12 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.commons.lang.StringUtils;
 
+import ch.systemsx.cisd.authentication.DefaultSessionManager;
+import ch.systemsx.cisd.authentication.DummyAuthenticationService;
 import ch.systemsx.cisd.authentication.IAuthenticationService;
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.collections.CollectionUtils;
@@ -40,6 +44,8 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.serviceconversation.ServiceConversationDTO;
 import ch.systemsx.cisd.common.serviceconversation.ServiceMessage;
 import ch.systemsx.cisd.common.serviceconversation.server.ServiceConversationServer;
+import ch.systemsx.cisd.common.servlet.IRequestContextProvider;
+import ch.systemsx.cisd.common.servlet.RequestContextProviderAdapter;
 import ch.systemsx.cisd.common.spring.HttpInvokerUtils;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.server.api.v1.SearchCriteriaToDetailedSearchCriteriaTranslator;
@@ -72,6 +78,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISampleTypeDAO;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
+import ch.systemsx.cisd.openbis.generic.shared.LogMessagePrefixGenerator;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchableEntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.EntityOperationsState;
@@ -191,25 +198,32 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
 
     private final ServiceConversationServer server;
 
+    private final IEntityOperationChecker entityOperationChecker;
+
+    private final DefaultSessionManager<Session> sessionManagerForEntityOperation;
+
     public ETLService(IAuthenticationService authenticationService,
             ISessionManager<Session> sessionManager, IDAOFactory daoFactory,
             ICommonBusinessObjectFactory boFactory, IDataStoreServiceFactory dssFactory,
-            TrustedCrossOriginDomainsProvider trustedOriginDomainProvider)
+            TrustedCrossOriginDomainsProvider trustedOriginDomainProvider,
+            IEntityOperationChecker entityOperationChecker)
     {
         this(authenticationService, sessionManager, daoFactory, null, boFactory, dssFactory,
-                trustedOriginDomainProvider);
+                trustedOriginDomainProvider, entityOperationChecker);
     }
 
     ETLService(IAuthenticationService authenticationService,
             ISessionManager<Session> sessionManager, IDAOFactory daoFactory,
             IPropertiesBatchManager propertiesBatchManager, ICommonBusinessObjectFactory boFactory,
             IDataStoreServiceFactory dssFactory,
-            TrustedCrossOriginDomainsProvider trustedOriginDomainProvider)
+            TrustedCrossOriginDomainsProvider trustedOriginDomainProvider,
+            IEntityOperationChecker entityOperationChecker)
     {
         super(authenticationService, sessionManager, daoFactory, propertiesBatchManager, boFactory);
         this.daoFactory = daoFactory;
         this.dssFactory = dssFactory;
         this.trustedOriginDomainProvider = trustedOriginDomainProvider;
+        this.entityOperationChecker = entityOperationChecker;
 
         org.hibernate.SessionFactory sessionFactory =
                 daoFactory.getPersistencyResources().getSessionFactoryOrNull();
@@ -217,6 +231,18 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         server = new ServiceConversationServer();
         server.addServiceType(new RmiServiceFactory<IETLLIMSService>(server, this,
                 IETLLIMSService.class, PROGRESS_TIMEOUT, sessionFactory));
+        sessionManagerForEntityOperation =
+                new DefaultSessionManager<Session>(new SessionFactory(),
+                        new LogMessagePrefixGenerator(), new DummyAuthenticationService(),
+                        new RequestContextProviderAdapter(new IRequestContextProvider()
+                            {
+                                @Override
+                                public HttpServletRequest getHttpServletRequest()
+                                {
+                                    return null;
+                                }
+                            }), 30);
+
     }
 
     @Override
@@ -1329,33 +1355,44 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
 
         EntityOperationsInProgress.getInstance().addRegistrationPending(registrationId);
 
+        String sessionTokenForEntityOperation = null;
         try
         {
-
             final Session session = getSession(sessionToken);
+            Session sessionForEntityOperation = session;
+            String userId = operationDetails.tryUserIdOrNull();
+            if (userId != null)
+            {
+                sessionTokenForEntityOperation =
+                        sessionManagerForEntityOperation.tryToOpenSession(userId, "dummy password");
+                sessionForEntityOperation =
+                        sessionManagerForEntityOperation.getSession(sessionTokenForEntityOperation);
+                injectPerson(sessionForEntityOperation, userId);
+            }
 
-            List<Space> spacesCreated = createSpaces(session, operationDetails, progressListener);
+            List<Space> spacesCreated =
+                    createSpaces(sessionForEntityOperation, operationDetails, progressListener);
 
             List<Material> materialsCreated =
-                    createMaterials(session, operationDetails, progressListener);
+                    createMaterials(sessionForEntityOperation, operationDetails, progressListener);
 
             List<Project> projectsCreated =
-                    createProjects(session, operationDetails, progressListener);
+                    createProjects(sessionForEntityOperation, operationDetails, progressListener);
 
             List<Experiment> experimentsCreated =
-                    createExperiments(session, operationDetails, progressListener);
+                    createExperiments(sessionForEntityOperation, operationDetails, progressListener);
 
             List<Sample> samplesCreated =
-                    createSamples(session, operationDetails, progressListener);
+                    createSamples(sessionForEntityOperation, operationDetails, progressListener);
 
             List<Sample> samplesUpdated =
-                    updateSamples(session, operationDetails, progressListener);
+                    updateSamples(sessionForEntityOperation, operationDetails, progressListener);
 
             List<ExternalData> dataSetsCreated =
-                    createDataSets(session, operationDetails, progressListener);
+                    createDataSets(sessionForEntityOperation, operationDetails, progressListener);
 
             List<ExternalData> dataSetsUpdated =
-                    updateDataSets(session, operationDetails, progressListener);
+                    updateDataSets(sessionForEntityOperation, operationDetails, progressListener);
 
             // If the id is not null, the caller wants to persist the fact that the operation was
             // invoked and completed;
@@ -1371,6 +1408,10 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         } finally
         {
             EntityOperationsInProgress.getInstance().removeRegistrationPending(registrationId);
+            if (sessionTokenForEntityOperation != null)
+            {
+                sessionManagerForEntityOperation.closeSession(sessionTokenForEntityOperation);
+            }
         }
     }
 
@@ -1404,6 +1445,8 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
     {
         ArrayList<SpacePE> spacePEsCreated = new ArrayList<SpacePE>();
         List<NewSpace> newSpaces = operationDetails.getSpaceRegistrations();
+        assertSpaceCreationAllowed(session, newSpaces);
+
         int index = 0;
         for (NewSpace newSpace : newSpaces)
         {
@@ -1415,6 +1458,14 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         return SpaceTranslator.translate(spacePEsCreated);
     }
 
+    protected void assertSpaceCreationAllowed(Session session, List<NewSpace> newSpaces)
+    {
+        if (newSpaces != null && newSpaces.isEmpty() == false)
+        {
+            entityOperationChecker.assertSpaceCreationAllowed(session, newSpaces);
+        }
+    }
+
     private List<Material> createMaterials(Session session,
             AtomicEntityOperationDetails operationDetails, IProgressListener progress)
     {
@@ -1422,6 +1473,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
                 new MaterialHelper(session, businessObjectFactory, getDAOFactory(),
                         getPropertiesBatchManager());
         Map<String, List<NewMaterial>> materialRegs = operationDetails.getMaterialRegistrations();
+        assertMaterialCreationAllowed(session, materialRegs);
         List<Material> registeredMaterials = new ArrayList<Material>();
         int index = 0;
         for (Entry<String, List<NewMaterial>> newMaterialsEntry : materialRegs.entrySet())
@@ -1435,6 +1487,15 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         return registeredMaterials;
     }
 
+    protected void assertMaterialCreationAllowed(Session session,
+            Map<String, List<NewMaterial>> materials)
+    {
+        if (materials != null && materials.isEmpty() == false)
+        {
+            entityOperationChecker.assertMaterialCreationAllowed(session, materials);
+        }
+    }
+
     private SpacePE registerSpaceInternal(Session session, NewSpace newSpace,
             String registratorUserIdOrNull)
     {
@@ -1472,6 +1533,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
     {
         ArrayList<ProjectPE> projectPEsCreated = new ArrayList<ProjectPE>();
         List<NewProject> newProjects = operationDetails.getProjectRegistrations();
+        assertProjectCreationAllowed(session, newProjects);
         int index = 0;
         for (NewProject newProject : newProjects)
         {
@@ -1483,6 +1545,14 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         return ProjectTranslator.translate(projectPEsCreated);
     }
 
+    protected void assertProjectCreationAllowed(Session session, List<NewProject> newProjects)
+    {
+        if (newProjects != null && newProjects.isEmpty() == false)
+        {
+            entityOperationChecker.assertProjectCreationAllowed(session, newProjects);
+        }
+    }
+
     private ProjectPE registerProjectInternal(Session session, NewProject newProject,
             String registratorUserIdOrNull)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/EntityOperationChecker.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/EntityOperationChecker.java
new file mode 100644
index 0000000000000000000000000000000000000000..9dfbace7bab91899b67a06854342cb33bf556f28
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/EntityOperationChecker.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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.server;
+
+import java.util.List;
+import java.util.Map;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
+import ch.systemsx.cisd.openbis.generic.shared.dto.IAuthSession;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+
+/**
+ * Implementation of {@link IEntityOperationChecker} which does nothing because checking is done by
+ * aspects.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class EntityOperationChecker implements IEntityOperationChecker
+{
+
+    @Override
+    public void assertSpaceCreationAllowed(IAuthSession session, List<NewSpace> newSpaces)
+    {
+    }
+
+    @Override
+    public void assertMaterialCreationAllowed(IAuthSession session,
+            Map<String, List<NewMaterial>> materials)
+    {
+    }
+
+    @Override
+    public void assertProjectCreationAllowed(Session session, List<NewProject> newProjects)
+    {
+    }
+
+    @Override
+    public void assertExperimentCreationAllowed(Session session, List<NewExperiment> newExperiments)
+    {
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/IEntityOperationChecker.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/IEntityOperationChecker.java
new file mode 100644
index 0000000000000000000000000000000000000000..3a0c228b74aecc7ea4f42a7752aaec9fc07f7182
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/IEntityOperationChecker.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 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.server;
+
+import java.util.List;
+import java.util.Map;
+
+import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.AuthorizationGuard;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.Capability;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.RolesAllowed;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.NewExperimentPredicate;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.NewProjectPredicate;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
+import ch.systemsx.cisd.openbis.generic.shared.dto.IAuthSession;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+
+/**
+ * Checking methods to be invoked to check authorization in context of
+ * {@link ETLService#performEntityOperations(String, ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationDetails)}
+ * .
+ * 
+ * @author Franz-Josef Elmer
+ */
+public interface IEntityOperationChecker
+{
+    @RolesAllowed(RoleWithHierarchy.INSTANCE_ETL_SERVER)
+    public void assertSpaceCreationAllowed(IAuthSession session, List<NewSpace> newSpaces);
+
+    @RolesAllowed(RoleWithHierarchy.INSTANCE_ETL_SERVER)
+    public void assertMaterialCreationAllowed(IAuthSession session,
+            Map<String, List<NewMaterial>> materials);
+
+    @RolesAllowed(
+        { RoleWithHierarchy.SPACE_ADMIN, RoleWithHierarchy.SPACE_ETL_SERVER })
+    @Capability("CREATE_PROJECTS_VIA_DSS")
+    public void assertProjectCreationAllowed(Session session,
+            @AuthorizationGuard(guardClass = NewProjectPredicate.class)
+            List<NewProject> newProjects);
+
+    @RolesAllowed(
+        { RoleWithHierarchy.SPACE_ADMIN, RoleWithHierarchy.SPACE_ETL_SERVER })
+    @Capability("CREATE_PROJECTS_VIA_DSS")
+    public void assertExperimentCreationAllowed(Session session,
+            @AuthorizationGuard(guardClass = NewExperimentPredicate.class)
+            List<NewExperiment> newExperiments);
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/NewProjectPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/NewProjectPredicate.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3783534521c583e20c47d70c10ef3e526365748
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/predicate/NewProjectPredicate.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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 ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifierFactory;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
+
+/**
+ * Predicate for {@link NewProject}.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class NewProjectPredicate extends DelegatedPredicate<SpaceIdentifier, NewProject>
+{
+    public NewProjectPredicate()
+    {
+        super(new ExistingSpaceIdentifierPredicate());
+    }
+
+    @Override
+    public SpaceIdentifier tryConvert(NewProject project)
+    {
+        ProjectIdentifier identifier = new ProjectIdentifierFactory(project.getIdentifier()).createIdentifier();
+        return identifier;
+    }
+
+    @Override
+    public String getCandidateDescription()
+    {
+        return "new project";
+    }
+
+}
diff --git a/openbis/source/java/genericApplicationContext.xml b/openbis/source/java/genericApplicationContext.xml
index 7777c3cb1f06ebb14915b4a15487f4ad9a2e1ce4..3baf2dec7f089142f32ceaaf1e90bc0f179d8346 100644
--- a/openbis/source/java/genericApplicationContext.xml
+++ b/openbis/source/java/genericApplicationContext.xml
@@ -150,7 +150,10 @@
         <constructor-arg ref="common-business-object-factory" />
         <constructor-arg ref="dss-factory" />
         <constructor-arg ref="trusted-origin-domain-provider" />
+        <constructor-arg ref="entity-operation-checker" />
     </bean>
+    
+    <bean id="entity-operation-checker" class="ch.systemsx.cisd.openbis.generic.server.EntityOperationChecker"/>
 
     <!-- 
         // Transaction
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
index 4f3ba1cf2520197fc4fdbe407cd01a101e1d8752..64aad99535ff7403b67ec7ee1dc07a3960bb9944 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
@@ -134,6 +134,8 @@ public class ETLServiceTest extends AbstractServerTestCase
 
     private IDataStoreService dataStoreService;
 
+    private IEntityOperationChecker entityOperationChecker;
+
     @Override
     @BeforeMethod
     public final void setUp()
@@ -142,6 +144,7 @@ public class ETLServiceTest extends AbstractServerTestCase
         boFactory = context.mock(ICommonBusinessObjectFactory.class);
         dssfactory = context.mock(IDataStoreServiceFactory.class);
         dataStoreService = context.mock(IDataStoreService.class);
+        entityOperationChecker = context.mock(IEntityOperationChecker.class);
         MaterialConfigurationProvider.initializeForTesting(false);
     }
 
@@ -1025,7 +1028,7 @@ public class ETLServiceTest extends AbstractServerTestCase
             { "c1", "c2" });
 
         prepareEntityOperationsExpectations(samplePE, sampleUpdate, material, materialType,
-                newMaterial, newSamplePE, newSampleIdentifier, newSample, externalData,
+                materialRegistrations, newSamplePE, newSampleIdentifier, newSample, externalData,
                 updatedDataSetCode, dataSetUpdate);
 
         AtomicEntityOperationDetails details =
@@ -1054,7 +1057,7 @@ public class ETLServiceTest extends AbstractServerTestCase
 
     private void prepareEntityOperationsExpectations(final SamplePE samplePE,
             final SampleUpdatesDTO sampleUpdate, final MaterialPE material,
-            final MaterialTypePE materialType, final NewMaterial newMaterial,
+            final MaterialTypePE materialType, final Map<String, List<NewMaterial>> newMaterials,
             final SamplePE newSamplePE, final SampleIdentifier newSampleIdentifier,
             final NewSample newSample, final NewExternalData externalData,
             final String updatedDataSetCode, final DataSetUpdatesDTO dataSetUpdate)
@@ -1068,13 +1071,16 @@ public class ETLServiceTest extends AbstractServerTestCase
                     allowing(entityTypeDAO).tryToFindEntityTypeByCode(materialType.getCode());
                     will(returnValue(materialType));
 
-                    final List<NewMaterial> newMaterials = Arrays.asList(newMaterial);
-                    one(propertiesBatchManager).manageProperties(materialType, newMaterials, null);
+                    one(entityOperationChecker)
+                            .assertMaterialCreationAllowed(SESSION, newMaterials);
+                    List<NewMaterial> newMaterialsList = newMaterials.values().iterator().next();
+                    one(propertiesBatchManager).manageProperties(materialType, newMaterialsList,
+                            null);
 
                     one(boFactory).createMaterialTable(SESSION);
                     will(returnValue(materialTable));
 
-                    one(materialTable).add(newMaterials, materialType);
+                    one(materialTable).add(newMaterialsList, materialType);
                     one(materialTable).save();
                     one(materialTable).getMaterials();
                     will(returnValue(Arrays.asList(material)));
@@ -1193,7 +1199,7 @@ public class ETLServiceTest extends AbstractServerTestCase
             { "c1", "c2" });
 
         prepareEntityOperationsExpectations(samplePE, sampleUpdate, material, materialType,
-                newMaterial, newSamplePE, newSampleIdentifier, newSample, externalData,
+                materialRegistrations, newSamplePE, newSampleIdentifier, newSample, externalData,
                 updatedDataSetCode, dataSetUpdate);
 
         context.checking(new Expectations()
@@ -1367,7 +1373,7 @@ public class ETLServiceTest extends AbstractServerTestCase
     private IETLLIMSService createService()
     {
         return new ETLService(authenticationService, sessionManager, daoFactory,
-                propertiesBatchManager, boFactory, dssfactory, null);
+                propertiesBatchManager, boFactory, dssfactory, null, entityOperationChecker);
     }
 
     private DataStoreServerInfo createDSSInfo()
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/EntityOperationTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/EntityOperationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a07d5280e9613dc69157767139d5cbc1d025d1e1
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/EntityOperationTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 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.systemtest;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.fail;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException;
+import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewProject;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSpace;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode;
+import ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationDetails;
+import ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationResult;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetUpdatesDTO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
+
+/**
+ * System tests for
+ * {@link IETLLIMSService#performEntityOperations(String, AtomicEntityOperationDetails)}
+ * 
+ * @author Franz-Josef Elmer
+ */
+@Test(groups = "system test")
+public class EntityOperationTest extends SystemTestCase
+{
+    private static final String PREFIX = "EO_";
+
+    private static final String SPACE_ETL_SERVER_FOR_A = PREFIX + "S_ETL_A";
+
+    private static final SpaceIdentifier SPACE_A = new SpaceIdentifier("CISD", "CISD");
+
+    private static final SpaceIdentifier SPACE_B = new SpaceIdentifier("CISD", "TESTGROUP");
+
+    private static final class EntityOperationBuilder
+    {
+        private final List<NewSpace> spaces = new ArrayList<NewSpace>();
+
+        private final List<NewProject> projects = new ArrayList<NewProject>();
+
+        private final List<NewExperiment> experiments = new ArrayList<NewExperiment>();
+
+        private final List<NewSample> samples = new ArrayList<NewSample>();
+
+        private final List<SampleUpdatesDTO> sampleUpdates = new ArrayList<SampleUpdatesDTO>();
+
+        private final List<? extends NewExternalData> dataSets = new ArrayList<NewExternalData>();
+
+        private final List<DataSetUpdatesDTO> dataSetUpdates = new ArrayList<DataSetUpdatesDTO>();
+
+        private final Map<String, List<NewMaterial>> materials =
+                new HashMap<String, List<NewMaterial>>();
+
+        private TechId registrationID;
+
+        private String userID;
+
+        EntityOperationBuilder(long registrationID)
+        {
+            this.registrationID = new TechId(registrationID);
+        }
+
+        EntityOperationBuilder user(String userID)
+        {
+            this.userID = userID;
+            return this;
+        }
+
+        EntityOperationBuilder space(String code)
+        {
+            return space(new NewSpace(code, null, null));
+        }
+
+        EntityOperationBuilder space(NewSpace space)
+        {
+            spaces.add(space);
+            return this;
+        }
+
+        EntityOperationBuilder project(SpaceIdentifier spaceIdentifier, String projectCode)
+        {
+            String projectIdentifier =
+                    new ProjectIdentifier(spaceIdentifier, projectCode).toString();
+            return project(new NewProject(projectIdentifier, null));
+        }
+
+        EntityOperationBuilder project(NewProject project)
+        {
+            projects.add(project);
+            return this;
+        }
+
+        AtomicEntityOperationDetails create()
+        {
+            return new AtomicEntityOperationDetails(registrationID, userID, spaces, projects,
+                    experiments, sampleUpdates, samples, materials, dataSets, dataSetUpdates);
+        }
+
+    }
+
+    @BeforeClass
+    public void createTestUsers()
+    {
+        assignSpaceRole(registerPerson(SPACE_ETL_SERVER_FOR_A), RoleCode.ETL_SERVER, SPACE_A);
+    }
+
+    @Test
+    public void testCreateProjectAsSpaceETLServerSuccessfully()
+    {
+        String sessionToken = authenticateAs(SPACE_ETL_SERVER_FOR_A);
+        AtomicEntityOperationDetails eo =
+                new EntityOperationBuilder(1).project(SPACE_A, "P1").create();
+
+        AtomicEntityOperationResult result = etlService.performEntityOperations(sessionToken, eo);
+
+        assertEquals("[/" + SPACE_A.getSpaceCode() + "/P1]", result.getProjectsCreated().toString());
+    }
+
+    @Test
+    public void testCreateProjectAsSpaceETLServerThrowsAuthorizationFailure()
+    {
+        String sessionToken = authenticateAs(SPACE_ETL_SERVER_FOR_A);
+        AtomicEntityOperationDetails eo =
+                new EntityOperationBuilder(1).project(SPACE_B, "P1").create();
+
+        try
+        {
+            etlService.performEntityOperations(sessionToken, eo);
+            fail("AuthorizationFailureException expected");
+        } catch (AuthorizationFailureException ex)
+        {
+            assertEquals("Authorization failure: ERROR: \"User '" + SPACE_ETL_SERVER_FOR_A
+                    + "' does not have enough privileges.\".", ex.getMessage());
+        }
+    }
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/RelationshipServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/RelationshipServiceTest.java
index e49dc88547bda7650cd1f3e4c0944316766d1128..67aff37e592d201abfc02813846505402f62da92 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/RelationshipServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/RelationshipServiceTest.java
@@ -27,7 +27,6 @@ import org.testng.annotations.Test;
 import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSamplesWithTypes;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode;
@@ -42,33 +41,43 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 @Test(groups = "system test")
 public class RelationshipServiceTest extends SystemTestCase
 {
+    private static final String PREFIX = "RS_";
+
+    private static final String BASIC_USER = PREFIX + "basic_user";
+
+    private static final String POWER_USER = PREFIX + "power_user";
+
+    private static final String SOURCE_SPACE_ADMIN = PREFIX + "source_space_admin";
+
+    private static final String DESTINATION_SPACE_ADMIN = PREFIX + "destination_space_admin";
+
+    private static final String BOTH_SPACE_ADMIN = PREFIX + "both_space_admin";
+
+    private static final String INSTANCE_ADMIN = PREFIX + "instance_admin";
 
     private SpaceIdentifier sourceSpace = new SpaceIdentifier("CISD", "CISD");
 
     private SpaceIdentifier destinationSpace = new SpaceIdentifier("CISD", "TESTGROUP");
 
-    private String systemSessionToken;
-
     private ExperimentUpdatesDTO projectUpdate;
 
     @BeforeClass
     public void loginSystemUser()
     {
-        systemSessionToken = commonServer.tryToAuthenticateAsSystem().getSessionToken();
         projectUpdate = getProjectUpdate();
-        createSpaceUser("basic_user", RoleCode.USER, RoleCode.USER);
-        createSpaceUser("power_user", RoleCode.POWER_USER, RoleCode.POWER_USER);
-        createSpaceUser("source_space_admin", RoleCode.ADMIN, RoleCode.USER);
-        createSpaceUser("destination_space_admin", RoleCode.USER, RoleCode.ADMIN);
-        createSpaceUser("both_space_admin", RoleCode.ADMIN, RoleCode.ADMIN);
-        createInstanceUser("instance_admin", RoleCode.ADMIN);
+        createSpaceUser(BASIC_USER, RoleCode.USER, RoleCode.USER);
+        createSpaceUser(POWER_USER, RoleCode.POWER_USER, RoleCode.POWER_USER);
+        createSpaceUser(SOURCE_SPACE_ADMIN, RoleCode.ADMIN, RoleCode.USER);
+        createSpaceUser(DESTINATION_SPACE_ADMIN, RoleCode.USER, RoleCode.ADMIN);
+        createSpaceUser(BOTH_SPACE_ADMIN, RoleCode.ADMIN, RoleCode.ADMIN);
+        createInstanceUser(INSTANCE_ADMIN, RoleCode.ADMIN);
     }
 
     @Test(expectedExceptions =
         { AuthorizationFailureException.class })
     public void basicUserIsNotAllowedToUpdateExperienceProjectRelationship()
     {
-        String session = authenticate("basic_user");
+        String session = authenticateAs(BASIC_USER);
         commonServer.updateExperiment(session, projectUpdate);
     }
 
@@ -76,7 +85,7 @@ public class RelationshipServiceTest extends SystemTestCase
         { AuthorizationFailureException.class })
     public void powerUserIsNotAllowedToUpdateExperienceProjectRelationship()
     {
-        String session = authenticate("power_user");
+        String session = authenticateAs(POWER_USER);
         commonServer.updateExperiment(session, projectUpdate);
     }
 
@@ -84,7 +93,7 @@ public class RelationshipServiceTest extends SystemTestCase
         { AuthorizationFailureException.class })
     public void spaceAdminOfOnlySourceSpaceIsNotAllowedToUpdateExperienceProjectRelationship()
     {
-        String session = authenticate("source_space_admin");
+        String session = authenticateAs(SOURCE_SPACE_ADMIN);
         commonServer.updateExperiment(session, projectUpdate);
     }
 
@@ -92,64 +101,54 @@ public class RelationshipServiceTest extends SystemTestCase
         { AuthorizationFailureException.class })
     public void spaceAdminOfOnlyDestinationSpaceIsNotAllowedToUpdateExperienceProjectRelationship()
     {
-        String session = authenticate("destination_space_admin");
+        String session = authenticateAs(DESTINATION_SPACE_ADMIN);
         commonServer.updateExperiment(session, projectUpdate);
     }
 
     @Test
     public void spaceAdminOfBothSpacesIsAllowedToUpdateExperienceProjectRelationship()
     {
-        String session = authenticate("both_space_admin");
+        String session = authenticateAs(BOTH_SPACE_ADMIN);
         commonServer.updateExperiment(session, projectUpdate);
 
         Experiment experiment =
-                commonServer.getExperimentInfo(session,
-                        new ExperimentIdentifier(
-                                "CISD", "TESTGROUP", "TESTPROJ", "EXP1"));
+                commonServer.getExperimentInfo(session, new ExperimentIdentifier("CISD",
+                        "TESTGROUP", "TESTPROJ", "EXP1"));
         assertThat(experiment.getProject().getCode(), is("TESTPROJ"));
     }
 
     @Test
     public void instanceAdminIsAllowedToUpdateExperienceProjectRelationship()
     {
-        String session = authenticate("instance_admin");
+        String session = authenticateAs(INSTANCE_ADMIN);
         commonServer.updateExperiment(session, projectUpdate);
 
         Experiment experiment =
-                commonServer.getExperimentInfo(session,
-                        new ExperimentIdentifier(
-                                "CISD", "TESTGROUP", "TESTPROJ", "EXP1"));
+                commonServer.getExperimentInfo(session, new ExperimentIdentifier("CISD",
+                        "TESTGROUP", "TESTPROJ", "EXP1"));
         assertThat(experiment.getProject().getCode(), is("TESTPROJ"));
     }
 
-    private String authenticate(String user)
-    {
-        return commonServer.tryToAuthenticate(user, "password").getSessionToken();
-    }
-
     private void createSpaceUser(String userName, RoleCode sourceSpaceRole,
             RoleCode destinationSpaceRole)
     {
-        String sessionToken = commonServer.tryToAuthenticateAsSystem().getSessionToken();
-        commonServer.registerPerson(sessionToken, userName);
-        commonServer.registerSpaceRole(sessionToken, sourceSpaceRole,
-                sourceSpace, Grantee.createPerson(userName));
-        commonServer.registerSpaceRole(sessionToken, destinationSpaceRole,
-                destinationSpace, Grantee.createPerson(userName));
+        registerPerson(userName);
+        assignSpaceRole(userName, sourceSpaceRole, sourceSpace);
+        assignSpaceRole(userName, destinationSpaceRole, destinationSpace);
     }
 
     private void createInstanceUser(String userName, RoleCode role)
     {
-        commonServer.registerPerson(systemSessionToken, userName);
-        commonServer.registerInstanceRole(systemSessionToken, role, Grantee.createPerson(userName));
+        registerPerson(userName);
+        assignInstanceRole(userName, role);
     }
 
     private ExperimentUpdatesDTO getProjectUpdate()
     {
         ExperimentUpdatesDTO updates = new ExperimentUpdatesDTO();
         Experiment experiment =
-                commonServer.getExperimentInfo(systemSessionToken, new ExperimentIdentifier(
-                        "CISD", "CISD", "NEMO", "EXP1"));
+                commonServer.getExperimentInfo(systemSessionToken, new ExperimentIdentifier("CISD",
+                        "CISD", "NEMO", "EXP1"));
 
         updates.setExperimentId(new TechId(experiment));
         updates.setVersion(experiment.getModificationDate());
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java
index e97f1a2d360eef2eca8cf8218daedc62efb731d9..f026ace361aa41008ef69b4d5713739a9559c050 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/SystemTestCase.java
@@ -33,6 +33,7 @@ import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
 import org.springframework.test.context.transaction.TransactionConfiguration;
 import org.testng.AssertJUnit;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeSuite;
 
 import ch.systemsx.cisd.common.servlet.SpringRequestContextProvider;
@@ -43,15 +44,19 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.TypedTableResultSe
 import ch.systemsx.cisd.openbis.generic.client.web.server.UploadedFilesBean;
 import ch.systemsx.cisd.openbis.generic.server.ICommonServerForInternalUse;
 import ch.systemsx.cisd.openbis.generic.server.util.TestInitializer;
+import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.basic.GridRowModel;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.CodeWithRegistration;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DisplaySettings;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityPropertiesHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.PropertyBuilder;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.IGenericClientService;
 import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
 
@@ -76,14 +81,24 @@ public abstract class SystemTestCase extends AbstractTransactionalTestNGSpringCo
 
     protected IGenericClientService genericClientService;
 
+    protected IETLLIMSService etlService;
+
     protected MockHttpServletRequest request;
 
+    protected String systemSessionToken;
+
     @BeforeSuite
     public void beforeSuite()
     {
         TestInitializer.init();
     }
 
+    @BeforeClass
+    public void loginAsSystem()
+    {
+        systemSessionToken = commonServer.tryToAuthenticateAsSystem().getSessionToken();
+    }
+
     /**
      * Sets a {@link MockHttpServletRequest} for the specified context provider
      */
@@ -142,6 +157,13 @@ public abstract class SystemTestCase extends AbstractTransactionalTestNGSpringCo
         this.genericClientService = genericClientService;
     }
 
+    @Autowired
+    public void setETLService(IETLLIMSService etlService)
+    {
+        this.etlService = etlService;
+
+    }
+
     protected SessionContext logIntoCommonClientService()
     {
         SessionContext context = commonClientService.tryToLogin("test", "a");
@@ -209,6 +231,39 @@ public abstract class SystemTestCase extends AbstractTransactionalTestNGSpringCo
         }
     }
 
+    /**
+     * Register a person with specified user ID.
+     * 
+     * @return userID
+     */
+    protected String registerPerson(String userID)
+    {
+        commonServer.registerPerson(systemSessionToken, userID);
+        return userID;
+    }
+
+    protected void assignInstanceRole(String userID, RoleCode roleCode)
+    {
+        commonServer.registerInstanceRole(systemSessionToken, roleCode,
+                Grantee.createPerson(userID));
+    }
+
+    protected void assignSpaceRole(String userID, RoleCode roleCode, SpaceIdentifier spaceIdentifier)
+    {
+        commonServer.registerSpaceRole(systemSessionToken, roleCode, spaceIdentifier,
+                Grantee.createPerson(userID));
+    }
+
+    /**
+     * Authenticates as specified user.
+     * 
+     * @return session token
+     */
+    protected String authenticateAs(String user)
+    {
+        return commonServer.tryToAuthenticate(user, "password").getSessionToken();
+    }
+
     protected NewSampleBuilder sample(String identifier)
     {
         return new NewSampleBuilder(identifier);