From bec12b40ff70afc02de89c63f9994b4b8c46063f Mon Sep 17 00:00:00 2001
From: izabel <izabel>
Date: Wed, 8 Apr 2009 14:11:21 +0000
Subject: [PATCH] [LMS-826] extend PEs

SVN: 10634
---
 .../openbis/generic/server/ETLService.java    |  14 +-
 .../business/bo/AbstractBusinessObject.java   |   6 +-
 .../server/business/bo/ExperimentBO.java      |   6 +-
 ...AttachmentDAO.java => IAttachmentDAO.java} |  29 +--
 .../server/dataaccess/IDAOFactory.java        |   4 +-
 ...tAttachmentDAO.java => AttachmentDAO.java} |  94 ++++-----
 .../server/dataaccess/db/DAOFactory.java      |  10 +-
 .../shared/dto/AttachmentHolderPE.java        | 155 +++++++++++++++
 .../generic/shared/dto/AttachmentPE.java      | 101 ++++++++--
 .../generic/shared/dto/ColumnNames.java       |   2 +-
 .../generic/shared/dto/ExperimentPE.java      | 116 ++----------
 .../openbis/generic/shared/dto/ProjectPE.java |  57 +++++-
 .../openbis/generic/shared/dto/SamplePE.java  |  21 ++-
 .../shared/dto/ValidationMessages.java        | 178 ++++++++----------
 .../dto/hibernate/SearchFieldConstants.java   |   4 +
 openbis/source/java/hibernateContext.xml      |   2 +
 .../generic/server/ETLServiceTest.java        |  10 +-
 .../shared/AbstractServerTestCase.java        |   8 +-
 18 files changed, 515 insertions(+), 302 deletions(-)
 rename openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/{IExperimentAttachmentDAO.java => IAttachmentDAO.java} (60%)
 rename openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/{ExperimentAttachmentDAO.java => AttachmentDAO.java} (53%)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentHolderPE.java

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 c7110ba4d63..a3114289531 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
@@ -37,7 +37,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IExperimentBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IExternalDataBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentAttachmentDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAttachmentDAO;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
 import ch.systemsx.cisd.openbis.generic.shared.IWebService;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
@@ -216,10 +216,10 @@ public class ETLService extends AbstractServer<IETLService> implements IETLServi
         HibernateUtils.initialize(experiment.getProperties());
         final List<ProcessingInstructionDTO> instructions =
                 new ArrayList<ProcessingInstructionDTO>();
-        final IExperimentAttachmentDAO experimentAttachmentDAO =
-                daoFactory.getExperimentAttachmentDAO();
+        final IAttachmentDAO experimentAttachmentDAO =
+                daoFactory.getAttachmentDAO();
         final List<AttachmentPE> attachments =
-                experimentAttachmentDAO.listExperimentAttachments(experiment);
+                experimentAttachmentDAO.listAttachments(experiment);
         for (final AttachmentPE attachment : attachments)
         {
             final String fileName = attachment.getFileName();
@@ -273,10 +273,10 @@ public class ETLService extends AbstractServer<IETLService> implements IETLServi
             final String procedureTypeCode)
     {
         final String key = createKey(template, procedureTypeCode);
-        final IExperimentAttachmentDAO experimentAttachmentDAO =
-                daoFactory.getExperimentAttachmentDAO();
+        final IAttachmentDAO experimentAttachmentDAO =
+                daoFactory.getAttachmentDAO();
         final AttachmentPE attachment =
-                experimentAttachmentDAO.tryFindExpAttachmentByExpAndFileName(experiment, key);
+                experimentAttachmentDAO.tryFindAttachmentByOwnerAndFileName(experiment, key);
         if (attachment != null)
         {
             return attachment.getAttachmentContent().getValue();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
index b5c7450d7db..2742676ee63 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
@@ -24,7 +24,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataSetTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDatabaseInstanceDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityPropertyTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityTypeDAO;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentAttachmentDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAttachmentDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExternalDataDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IFileFormatTypeDAO;
@@ -168,9 +168,9 @@ abstract class AbstractBusinessObject implements IDAOFactory
         return daoFactory.getVocabularyDAO();
     }
 
-    public final IExperimentAttachmentDAO getExperimentAttachmentDAO()
+    public final IAttachmentDAO getAttachmentDAO()
     {
-        return daoFactory.getExperimentAttachmentDAO();
+        return daoFactory.getAttachmentDAO();
     }
 
     public IDataSetTypeDAO getDataSetTypeDAO()
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java
index e45cf0c5ea6..8a93e68f5ab 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ExperimentBO.java
@@ -26,7 +26,7 @@ import org.springframework.dao.DataAccessException;
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentAttachmentDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAttachmentDAO;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleProperty;
@@ -257,12 +257,12 @@ public final class ExperimentBO extends AbstractBusinessObject implements IExper
         }
         if (attachments.isEmpty() == false)
         {
-            final IExperimentAttachmentDAO experimentPropertyDAO = getExperimentAttachmentDAO();
+            final IAttachmentDAO experimentPropertyDAO = getAttachmentDAO();
             for (final AttachmentPE property : attachments)
             {
                 try
                 {
-                    experimentPropertyDAO.createExperimentAttachment(property, experiment);
+                    experimentPropertyDAO.createAttachment(property, experiment);
                 } catch (final DataAccessException e)
                 {
                     final String fileName = property.getFileName();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExperimentAttachmentDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAttachmentDAO.java
similarity index 60%
rename from openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExperimentAttachmentDAO.java
rename to openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAttachmentDAO.java
index 5fc08e98b9f..dd7da5568d5 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IExperimentAttachmentDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAttachmentDAO.java
@@ -20,7 +20,7 @@ import java.util.List;
 
 import org.springframework.dao.DataAccessException;
 
-import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentHolderPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
 
 /**
@@ -28,16 +28,15 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
  * 
  * @author Franz-Josef Elmer
  */
-public interface IExperimentAttachmentDAO
+public interface IAttachmentDAO
 {
     /**
      * Returns a list of the descriptions of all {@link AttachmentPE} object associated with the
-     * specified experiment. The result is detached from the hibernate session.
+     * specified {@link AttachmentHolderPE}. The result is detached from the hibernate session.
      * 
-     * @param experiment Technical ID of the experiment whose properties are requested.
+     * @param owner Technical ID of the {@link AttachmentHolderPE} whose properties are requested.
      */
-    public List<AttachmentPE> listExperimentAttachments(ExperimentPE experiment)
-            throws DataAccessException;
+    public List<AttachmentPE> listAttachments(AttachmentHolderPE owner) throws DataAccessException;
 
     /**
      * Creates a persistent version of the specified attachment. Registrator and version are not
@@ -46,27 +45,29 @@ public interface IExperimentAttachmentDAO
      * @param attachment The property to register.
      * @param owner Owner of the attachment. Should be a persistent object.
      */
-    public void createExperimentAttachment(AttachmentPE attachment, ExperimentPE owner)
+    public void createAttachment(AttachmentPE attachment, AttachmentHolderPE owner)
             throws DataAccessException;
 
     /**
-     * Finds the attachment requested by the specified experiment code and file name. If multiple
-     * versions of this attachment exist, this will always return the latest version.
+     * Finds the attachment requested by the specified {@link AttachmentHolderPE} code and file
+     * name. If multiple versions of this attachment exist, this will always return the latest
+     * version.
      * 
-     * @param experiment technical ID of the associated experiment.
+     * @param owner technical ID of the associated {@link AttachmentHolderPE}.
      * @return <code>null</code> if no attachment is found. The result is detached from the
      *         hibernate session.
      */
-    public AttachmentPE tryFindExpAttachmentByExpAndFileName(ExperimentPE experiment,
+    public AttachmentPE tryFindAttachmentByOwnerAndFileName(AttachmentHolderPE owner,
             String fileName) throws DataAccessException;
 
     /**
-     * Finds the attachment requested by the specified experiment code, file name and version.
+     * Finds the attachment requested by the specified {@link AttachmentHolderPE} code, file name
+     * and version.
      * 
-     * @param experiment technical ID of the associated experiment.
+     * @param owner technical ID of the associated {@link AttachmentHolderPE}.
      * @return <code>null</code> if no attachment is found. The result is detached from the
      *         hibernate session.
      */
-    public AttachmentPE tryFindExpAttachmentByExpAndFileNameAndVersion(ExperimentPE experiment,
+    public AttachmentPE tryFindAttachmentByOwnerAndFileNameAndVersion(AttachmentHolderPE owner,
             String fileName, int version) throws DataAccessException;
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java
index d5db673d6d9..a9afc29f50e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDAOFactory.java
@@ -72,8 +72,8 @@ public interface IDAOFactory extends IAuthorizationDAOFactory
      */
     public IVocabularyDAO getVocabularyDAO();
 
-    /** Returns an implementation of {@link IExperimentAttachmentDAO}. */
-    public IExperimentAttachmentDAO getExperimentAttachmentDAO();
+    /** Returns an implementation of {@link IAttachmentDAO}. */
+    public IAttachmentDAO getAttachmentDAO();
 
     /** Returns an implementation of {@link IDataSetTypeDAO}. */
     public IDataSetTypeDAO getDataSetTypeDAO();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExperimentAttachmentDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AttachmentDAO.java
similarity index 53%
rename from openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExperimentAttachmentDAO.java
rename to openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AttachmentDAO.java
index 93f483f4233..1c35cfbfade 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/ExperimentAttachmentDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AttachmentDAO.java
@@ -25,35 +25,35 @@ import org.springframework.orm.hibernate3.HibernateTemplate;
 
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentAttachmentDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAttachmentDAO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentHolderPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 
 /**
- * Implementation of {@link IExperimentAttachmentDAO} for data bases.
+ * Implementation of {@link IAttachmentDAO} for data bases.
  * 
  * @author Franz-Josef Elmer
  * @author Tomasz Pylak
  */
-final class ExperimentAttachmentDAO extends AbstractDAO implements IExperimentAttachmentDAO
+final class AttachmentDAO extends AbstractDAO implements IAttachmentDAO
 {
     private final static Class<AttachmentPE> ATTACHMENT_CLASS = AttachmentPE.class;
 
     private final static String TABLE_NAME = ATTACHMENT_CLASS.getSimpleName();
 
     private static final Logger operationLog =
-            LogFactory.getLogger(LogCategory.OPERATION, ExperimentAttachmentDAO.class);
+            LogFactory.getLogger(LogCategory.OPERATION, AttachmentDAO.class);
 
-    ExperimentAttachmentDAO(final SessionFactory sessionFactory,
-            final DatabaseInstancePE databaseInstance)
+    AttachmentDAO(final SessionFactory sessionFactory, final DatabaseInstancePE databaseInstance)
     {
         super(sessionFactory, databaseInstance);
     }
 
-    private final int findLastVersion(final AttachmentPE fileAttachment, final ExperimentPE owner)
+    private final int findLastVersion(final AttachmentPE fileAttachment,
+            final AttachmentHolderPE owner)
     {
-        final String query = createFindLastVersionQuery();
+        final String query = createFindLastVersionQuery(owner);
         final List<Integer> versions =
                 cast(getHibernateTemplate().find(query,
                         toArray(owner, fileAttachment.getFileName())));
@@ -61,19 +61,25 @@ final class ExperimentAttachmentDAO extends AbstractDAO implements IExperimentAt
         return lastVersion == null ? 0 : lastVersion.intValue();
     }
 
-    private final static String createFindLastVersionQuery()
+    private final static String createFindLastVersionQuery(AttachmentHolderPE owner)
     {
-        return String
-                .format("select max(version) from %s where parentInternal = ? and fileName = ?",
-                        TABLE_NAME);
+        String ownerAsParent = getParentName(owner);
+        return String.format("select max(version) from %s where " + ownerAsParent
+                + " = ? and fileName = ?", TABLE_NAME);
+    }
+
+    private static String getParentName(AttachmentHolderPE owner)
+    {
+        String ownerAsParent = owner.getHolderName() + "ParentInternal";
+        return ownerAsParent;
     }
 
     //
-    // IExperimentAttachmentDAO
+    // IAttachmentDAO
     //
 
-    public final void createExperimentAttachment(final AttachmentPE attachment,
-            final ExperimentPE owner) throws DataAccessException
+    public final void createAttachment(final AttachmentPE attachment, final AttachmentHolderPE owner)
+            throws DataAccessException
     {
         assert attachment != null : "Unspecified attachment";
         assert attachment.getAttachmentContent() != null : "Unspecified attachment content.";
@@ -93,66 +99,66 @@ final class ExperimentAttachmentDAO extends AbstractDAO implements IExperimentAt
         }
     }
 
-    public final List<AttachmentPE> listExperimentAttachments(final ExperimentPE experiment)
+    public final List<AttachmentPE> listAttachments(final AttachmentHolderPE owner)
             throws DataAccessException
     {
-        assert experiment != null : "Unspecified parent experiment.";
+        assert owner != null : "Unspecified attachment holder.";
 
-        final String query = String.format("from %s where parentInternal = ?", TABLE_NAME);
-        final List<AttachmentPE> result =
-                cast(getHibernateTemplate().find(query, toArray(experiment)));
+        final String query =
+                String.format("from %s where " + getParentName(owner) + " = ?", TABLE_NAME);
+        final List<AttachmentPE> result = cast(getHibernateTemplate().find(query, toArray(owner)));
         if (operationLog.isDebugEnabled())
         {
-            operationLog.debug(String.format("%d attachment(s) found for experiment '%s'.", result
-                    .size(), experiment));
+            operationLog.debug(String.format("%d attachment(s) found for " + owner.getHolderName()
+                    + " '%s'.", result.size(), owner));
         }
         return result;
     }
 
-    public final AttachmentPE tryFindExpAttachmentByExpAndFileName(final ExperimentPE experiment,
+    public final AttachmentPE tryFindAttachmentByOwnerAndFileName(final AttachmentHolderPE owner,
             final String fileName) throws DataAccessException
     {
         assert fileName != null : "Unspecified file name.";
-        assert experiment != null : "Unspecified parent experiment.";
+        assert owner != null : "Unspecified parent attachment holder.";
 
         final String query =
-                String.format("from %s where parentInternal = ? and fileName = ? and version = ("
-                        + createFindLastVersionQuery() + ")", TABLE_NAME);
+                String.format("from %s where " + getParentName(owner)
+                        + " = ? and fileName = ? and version = ("
+                        + createFindLastVersionQuery(owner) + ")", TABLE_NAME);
         final List<AttachmentPE> result =
-                cast(getHibernateTemplate().find(query,
-                        toArray(experiment, fileName, experiment, fileName)));
-        final AttachmentPE attachment = tryFindEntity(result, "attachment", experiment, fileName);
+                cast(getHibernateTemplate().find(query, toArray(owner, fileName, owner, fileName)));
+        final AttachmentPE attachment = tryFindEntity(result, "attachment", owner, fileName);
         if (operationLog.isDebugEnabled())
         {
-            operationLog.debug(String.format("%s found for experiment '%s' and file name '%s'.",
-                    attachment == null ? "No attachment" : "Attachment '" + attachment + "'",
-                    experiment, fileName));
+            operationLog.debug(String.format("%s found for " + owner.getHolderName()
+                    + " '%s' and file name '%s'.", attachment == null ? "No attachment"
+                    : "Attachment '" + attachment + "'", owner, fileName));
         }
         return attachment;
     }
 
-    public final AttachmentPE tryFindExpAttachmentByExpAndFileNameAndVersion(
-            final ExperimentPE experiment, final String fileName, final int version)
+    public final AttachmentPE tryFindAttachmentByOwnerAndFileNameAndVersion(
+            final AttachmentHolderPE owner, final String fileName, final int version)
             throws DataAccessException
     {
-        assert experiment != null : "Unspecified experiment.";
+        assert owner != null : "Unspecified attachment holder.";
         assert fileName != null : "Unspecified file name.";
         assert version > 0 : "Version must be > 0.";
 
         final String query =
-                String.format("from %s where parentInternal = ? and fileName = ? and version = ?",
-                        TABLE_NAME);
+                String.format("from %s where " + getParentName(owner)
+                        + " = ? and fileName = ? and version = ?", TABLE_NAME);
         final List<AttachmentPE> result =
-                cast(getHibernateTemplate().find(query, toArray(experiment, fileName, version)));
+                cast(getHibernateTemplate().find(query, toArray(owner, fileName, version)));
         final AttachmentPE attachment =
-                tryFindEntity(result, "attachment", experiment, fileName, version);
+                tryFindEntity(result, "attachment", owner, fileName, version);
         if (operationLog.isDebugEnabled())
         {
-            operationLog.debug(String.format(
-                    "%s found for experiment '%s', file name '%s' and version %d.",
-                    attachment == null ? "No attachment" : "Attachment '" + attachment + "'",
-                    experiment, fileName, version));
+            operationLog.debug(String.format("%s found for " + owner.getHolderName()
+                    + " '%s', file name '%s' and version %d.", attachment == null ? "No attachment"
+                    : "Attachment '" + attachment + "'", owner, fileName, version));
         }
         return attachment;
     }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
index b6ccb96e0cb..459058f29b8 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
@@ -26,7 +26,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataSetTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityPropertyTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityTypeDAO;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentAttachmentDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAttachmentDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IFileFormatTypeDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IHibernateSearchDAO;
@@ -68,7 +68,7 @@ public final class DAOFactory extends AuthorizationDAOFactory implements IDAOFac
 
     private final IVocabularyDAO vocabularyDAO;
 
-    private final IExperimentAttachmentDAO experimentAttachmentDAO;
+    private final IAttachmentDAO attachmentDAO;
 
     private final DataSetTypeDAO dataSetTypeDAO;
 
@@ -92,7 +92,7 @@ public final class DAOFactory extends AuthorizationDAOFactory implements IDAOFac
         experimentDAO = new ExperimentDAO(sessionFactory, databaseInstance);
         projectDAO = new ProjectDAO(sessionFactory, databaseInstance);
         vocabularyDAO = new VocabularyDAO(sessionFactory, databaseInstance);
-        experimentAttachmentDAO = new ExperimentAttachmentDAO(sessionFactory, databaseInstance);
+        attachmentDAO = new AttachmentDAO(sessionFactory, databaseInstance);
         dataSetTypeDAO = new DataSetTypeDAO(sessionFactory, databaseInstance);
         fileFormatTypeDAO = new FileFormatTypeDAO(sessionFactory, databaseInstance);
         locatorTypeDAO = new LocatorTypeDAO(sessionFactory, databaseInstance);
@@ -158,9 +158,9 @@ public final class DAOFactory extends AuthorizationDAOFactory implements IDAOFac
         return vocabularyDAO;
     }
 
-    public final IExperimentAttachmentDAO getExperimentAttachmentDAO()
+    public final IAttachmentDAO getAttachmentDAO()
     {
-        return experimentAttachmentDAO;
+        return attachmentDAO;
     }
 
     public IDataSetTypeDAO getDataSetTypeDAO()
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentHolderPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentHolderPE.java
new file mode 100644
index 00000000000..06b9bba45a7
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentHolderPE.java
@@ -0,0 +1,155 @@
+/*
+ * 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.dto;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Transient;
+
+import ch.rinn.restrictions.Private;
+import ch.systemsx.cisd.openbis.generic.shared.GenericSharedConstants;
+import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
+
+/**
+ * Encapsulates collection of {@link AttachmentPE}s with useful logic.
+ * 
+ * @author Izabela Adamczyk
+ */
+@MappedSuperclass
+public abstract class AttachmentHolderPE implements Serializable
+{
+    //
+    // Version
+    //
+    private static final long serialVersionUID = GenericSharedConstants.VERSION;
+
+    //
+    // Constants
+    //
+    public static final char HIDDEN_EXPERIMENT_PROPERTY_PREFIX_CHARACTER = '$';
+
+    public static final String HIDDEN_EXPERIMENT_PROPERTY_PREFIX =
+            Character.toString(HIDDEN_EXPERIMENT_PROPERTY_PREFIX_CHARACTER);
+
+    public static final String HIDDEN_EXPERIMENT_PROPERTY_PREFIX2 =
+            HIDDEN_EXPERIMENT_PROPERTY_PREFIX + HIDDEN_EXPERIMENT_PROPERTY_PREFIX;
+
+    //
+    // Fields
+    //
+    protected Set<AttachmentPE> attachments = new HashSet<AttachmentPE>();
+
+    private boolean attachmentsUnescaped = false;
+
+    //
+    // Abstract methods
+    //
+    @Transient
+    abstract public Set<AttachmentPE> getInternalAttachments();
+
+    @Transient
+    abstract public String getHolderName();
+
+    //
+    //
+    //
+    @Private
+    public void setInternalAttachments(final Set<AttachmentPE> attachments)
+    // for Hibernate and bean conversion only
+    {
+        this.attachments = attachments;
+    }
+
+    @Transient
+    public final Set<AttachmentPE> getAttachments()
+    {
+        final Set<AttachmentPE> set = new HashSet<AttachmentPE>(getInternalAttachments());
+        if (attachmentsUnescaped == false)
+        {
+            for (final Iterator<AttachmentPE> iter = set.iterator(); iter.hasNext(); /**/)
+            {
+                final AttachmentPE property = iter.next();
+                final boolean isHiddenFile = isHiddenFile(property.getFileName());
+                if (isHiddenFile)
+                {
+                    iter.remove();
+                }
+                unescapeFileName(property);
+            }
+            attachmentsUnescaped = true;
+        }
+        return set;
+    }
+
+    final void setAttachments(final Set<AttachmentPE> attachments)
+    // Package visibility to avoid bean conversion which will call an uninitialized field.
+    {
+        getInternalAttachments().clear();
+        for (final AttachmentPE attachment : attachments)
+        {
+            addAttachment(attachment);
+        }
+    }
+
+    public void addAttachment(final AttachmentPE child)
+    {
+        final AttachmentHolderPE parent = child.getParent();
+        if (parent != null)
+        {
+            parent.getInternalAttachments().remove(child);
+        }
+        child.setParent(this);
+        getInternalAttachments().add(child);
+    }
+
+    public final static boolean isHiddenFile(final String fileName)
+    {
+        return fileName.startsWith(HIDDEN_EXPERIMENT_PROPERTY_PREFIX)
+                && (fileName.length() == 1 || fileName.charAt(1) != HIDDEN_EXPERIMENT_PROPERTY_PREFIX_CHARACTER);
+    }
+
+    public final static String escapeFileName(final String fileName)
+    {
+        if (fileName != null && fileName.startsWith(HIDDEN_EXPERIMENT_PROPERTY_PREFIX))
+        {
+            return HIDDEN_EXPERIMENT_PROPERTY_PREFIX + fileName;
+        }
+        return fileName;
+    }
+
+    public final static void unescapeFileName(final AttachmentPE attachment)
+    {
+        if (attachment != null)
+        {
+            final String fileName = attachment.getFileName();
+            if (fileName != null && fileName.startsWith(HIDDEN_EXPERIMENT_PROPERTY_PREFIX2))
+            {
+                attachment.setFileName(fileName.substring(1));
+            }
+        }
+    }
+
+    public void ensureAttachmentsLoaded()
+    {
+        HibernateUtils.initialize(getInternalAttachments());
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentPE.java
index 87b91dc6864..d4b5da2807c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentPE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AttachmentPE.java
@@ -30,6 +30,7 @@ import javax.persistence.ManyToOne;
 import javax.persistence.OneToOne;
 import javax.persistence.SequenceGenerator;
 import javax.persistence.Table;
+import javax.persistence.Transient;
 import javax.persistence.UniqueConstraint;
 
 import org.apache.commons.lang.builder.EqualsBuilder;
@@ -59,8 +60,16 @@ import ch.systemsx.cisd.openbis.generic.shared.util.EqualsHashUtils;
  */
 @Entity
 @Table(name = TableNames.ATTACHMENTS_TABLE, uniqueConstraints =
-    { @UniqueConstraint(columnNames =
-        { ColumnNames.EXPERIMENT_COLUMN, ColumnNames.FILE_NAME_COLUMN, ColumnNames.VERSION_COLUMN }) })
+    {
+            @UniqueConstraint(columnNames =
+                { ColumnNames.EXPERIMENT_COLUMN, ColumnNames.FILE_NAME_COLUMN,
+                        ColumnNames.VERSION_COLUMN }),
+            @UniqueConstraint(columnNames =
+                { ColumnNames.SAMPLE_COLUMN, ColumnNames.FILE_NAME_COLUMN,
+                        ColumnNames.VERSION_COLUMN }),
+            @UniqueConstraint(columnNames =
+                { ColumnNames.PROJECT_COLUMN, ColumnNames.FILE_NAME_COLUMN,
+                        ColumnNames.VERSION_COLUMN }) })
 @ClassBridge(impl = AttachmentPE.AttachmentSearchBridge.class, index = Index.TOKENIZED, store = Store.NO)
 public class AttachmentPE extends HibernateAbstractRegistrationHolder implements Serializable,
         Comparable<AttachmentPE>
@@ -80,7 +89,11 @@ public class AttachmentPE extends HibernateAbstractRegistrationHolder implements
     /**
      * Parent (e.g. an experiment) to which this attachment belongs.
      */
-    private ExperimentPE parent;
+    private ExperimentPE experimentParent;
+
+    private SamplePE sampleParent;
+
+    private ProjectPE projectParent;
 
     /**
      * This bridge allows to save in the search index not only the content of the attachment, but
@@ -166,26 +179,25 @@ public class AttachmentPE extends HibernateAbstractRegistrationHolder implements
     }
 
     @ManyToOne(fetch = FetchType.LAZY)
-    @NotNull(message = ValidationMessages.EXPERIMENT_NOT_NULL_MESSAGE)
     @JoinColumn(name = ColumnNames.EXPERIMENT_COLUMN, updatable = false)
     @Private
     // for Hibernate and bean conversion only
     @ContainedIn
-    public ExperimentPE getParentInternal()
+    public ExperimentPE getExperimentParentInternal()
     {
-        return parent;
+        return experimentParent;
     }
 
     @Private
     // for Hibernate and bean conversion only
-    public void setParentInternal(final ExperimentPE parent)
+    public void setExperimentParentInternal(final ExperimentPE parent)
     {
-        this.parent = parent;
+        this.experimentParent = parent;
     }
 
     @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
     @NotNull(message = ValidationMessages.ATTACHMENT_CONTENT_NOT_NULL_MESSAGE)
-    @JoinColumn(name = ColumnNames.EXPERIMENT_ATTACHMENT_CONTENT_COLUMN, updatable = false)
+    @JoinColumn(name = ColumnNames.ATTACHMENT_CONTENT_COLUMN, updatable = false)
     public AttachmentContentPE getAttachmentContent()
     {
         return attachmentContent;
@@ -213,7 +225,7 @@ public class AttachmentPE extends HibernateAbstractRegistrationHolder implements
         final EqualsBuilder builder = new EqualsBuilder();
         builder.append(getFileName(), that.getFileName());
         builder.append(getVersion(), that.getVersion());
-        builder.append(getParentInternal(), that.getParentInternal());
+        builder.append(getParent(), that.getParent());
         return builder.isEquals();
     }
 
@@ -223,7 +235,7 @@ public class AttachmentPE extends HibernateAbstractRegistrationHolder implements
         final HashCodeBuilder builder = new HashCodeBuilder();
         builder.append(getFileName());
         builder.append(getVersion());
-        builder.append(getParentInternal());
+        builder.append(getParent());
         return builder.toHashCode();
     }
 
@@ -235,7 +247,7 @@ public class AttachmentPE extends HibernateAbstractRegistrationHolder implements
                         ModifiedShortPrefixToStringStyle.MODIFIED_SHORT_PREFIX_STYLE);
         builder.append("fileName", getFileName());
         builder.append("version", getVersion());
-        builder.append("parent", getParentInternal());
+        builder.append("parent", getParent());
         return builder.toString();
     }
 
@@ -249,4 +261,69 @@ public class AttachmentPE extends HibernateAbstractRegistrationHolder implements
         return byFile == 0 ? Integer.valueOf(getVersion()).compareTo(o.getVersion()) : byFile;
     }
 
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = ColumnNames.SAMPLE_COLUMN, updatable = false)
+    @Private
+    // for Hibernate and bean conversion only
+    @ContainedIn
+    public SamplePE getSampleParentInternal()
+    {
+        return sampleParent;
+    }
+
+    @Private
+    // for Hibernate and bean conversion only
+    public void setSampleParentInternal(final SamplePE parent)
+    {
+        this.sampleParent = parent;
+    }
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = ColumnNames.PROJECT_COLUMN, updatable = false)
+    @Private
+    // for Hibernate and bean conversion only
+    @ContainedIn
+    public ProjectPE getProjectParentInternal()
+    {
+        return projectParent;
+    }
+
+    @Private
+    // for Hibernate and bean conversion only
+    public void setProjectParentInternal(final ProjectPE parent)
+    {
+        this.projectParent = parent;
+    }
+
+    @Transient
+    public AttachmentHolderPE getParent()
+    {
+        if (getExperimentParentInternal() != null)
+        {
+            return getExperimentParentInternal();
+        } else if (getSampleParentInternal() != null)
+        {
+            return getSampleParentInternal();
+        } else if (getProjectParentInternal() != null)
+        {
+            return getProjectParentInternal();
+        }
+        return null;
+    }
+
+    public void setParent(AttachmentHolderPE owner)
+    {
+        if (owner instanceof ExperimentPE)
+        {
+            setExperimentParentInternal((ExperimentPE) owner);
+        } else if (owner instanceof SamplePE)
+        {
+            setSampleParentInternal((SamplePE) owner);
+        } else if (owner instanceof ProjectPE)
+        {
+            setProjectParentInternal((ProjectPE) owner);
+        }
+        throw new IllegalStateException("Unexpected attachment holder.");
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java
index 11803ff7f19..265d6101c0c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java
@@ -51,7 +51,7 @@ public final class ColumnNames {
 
 	public static final String DOWNLOAD_URL_COLUMN = "download_url";
 
-	public static final String EXPERIMENT_ATTACHMENT_CONTENT_COLUMN = "exac_id";
+	public static final String ATTACHMENT_CONTENT_COLUMN = "exac_id";
 
 	public static final String EXPERIMENT_COLUMN = "expe_id";
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
index 4adf9c822d9..9b77f50382a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
@@ -20,7 +20,6 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -70,7 +69,6 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifi
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.IdentifierHelper;
 import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.util.EqualsHashUtils;
-import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
 
 /**
  * Persistence Entity representing experiment.
@@ -83,21 +81,15 @@ import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
         { ColumnNames.CODE_COLUMN, ColumnNames.PROJECT_COLUMN }) })
 @Indexed
 @Friend(toClasses = AttachmentPE.class)
-public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyPE>,
-        IIdAndCodeHolder, Comparable<ExperimentPE>, IMatchingEntity, Serializable
+public class ExperimentPE extends AttachmentHolderPE implements
+        IEntityPropertiesHolder<ExperimentPropertyPE>, IIdAndCodeHolder, Comparable<ExperimentPE>,
+        IMatchingEntity, Serializable
+
 {
     private static final long serialVersionUID = GenericSharedConstants.VERSION;
 
     public static final ExperimentPE[] EMPTY_ARRAY = new ExperimentPE[0];
 
-    public static final char HIDDEN_EXPERIMENT_PROPERTY_PREFIX_CHARACTER = '$';
-
-    public static final String HIDDEN_EXPERIMENT_PROPERTY_PREFIX =
-            Character.toString(HIDDEN_EXPERIMENT_PROPERTY_PREFIX_CHARACTER);
-
-    public static final String HIDDEN_EXPERIMENT_PROPERTY_PREFIX2 =
-            HIDDEN_EXPERIMENT_PROPERTY_PREFIX + HIDDEN_EXPERIMENT_PROPERTY_PREFIX;
-
     private transient Long id;
 
     private String code;
@@ -114,10 +106,6 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
 
     private DataStorePE dataStore;
 
-    private Set<AttachmentPE> attachments = new HashSet<AttachmentPE>();
-
-    private boolean attachmentsUnescaped = false;
-    
     private List<SamplePE> samples = new ArrayList<SamplePE>();
 
     private List<DataPE> dataSets = new ArrayList<DataPE>();
@@ -297,7 +285,8 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
         getExperimentProperties().add(property);
     }
 
-    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "parentInternal")
+    @Override
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "experimentParentInternal")
     @IndexedEmbedded(prefix = SearchFieldConstants.PREFIX_EXPERIMENT_ATTACHMENTS)
     @Private
     // for Hibernate and bean conversion only
@@ -306,55 +295,6 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
         return attachments;
     }
 
-    @Private
-    // for Hibernate and bean conversion only
-    public void setInternalAttachments(final Set<AttachmentPE> attachments)
-    {
-        this.attachments = attachments;
-    }
-
-    @Transient
-    public final Set<AttachmentPE> getAttachments()
-    {
-        final Set<AttachmentPE> set = new HashSet<AttachmentPE>(getInternalAttachments());
-        if (attachmentsUnescaped == false)
-        {
-            for (final Iterator<AttachmentPE> iter = set.iterator(); iter.hasNext(); /**/)
-            {
-                final AttachmentPE property = iter.next();
-                final boolean isHiddenFile = isHiddenFile(property.getFileName());
-                if (isHiddenFile)
-                {
-                    iter.remove();
-                }
-                unescapeFileName(property);
-            }
-            attachmentsUnescaped = true;
-        }
-        return set;
-    }
-
-    // Package visibility to avoid bean conversion which will call an uninitialized field.
-    final void setAttachments(final Set<AttachmentPE> attachments)
-    {
-        getInternalAttachments().clear();
-        for (final AttachmentPE attachment : attachments)
-        {
-            addAttachment(attachment);
-        }
-    }
-
-    public void addAttachment(final AttachmentPE child)
-    {
-        final ExperimentPE parent = child.getParentInternal();
-        if (parent != null)
-        {
-            parent.getInternalAttachments().remove(child);
-        }
-        child.setParentInternal(this);
-        getInternalAttachments().add(child);
-    }
-    
     @Transient
     public List<SamplePE> getSamples()
     {
@@ -375,7 +315,7 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
     {
         this.samples = samples;
     }
-    
+
     public void setSamples(List<SamplePE> samples)
     {
         getExperimentSamples().clear();
@@ -384,7 +324,7 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
             addSample(sample);
         }
     }
-    
+
     public void addSample(SamplePE sample)
     {
         ExperimentPE experiment = sample.getExperiment();
@@ -526,33 +466,6 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
         return builder.toString();
     }
 
-    public final static void unescapeFileName(final AttachmentPE attachment)
-    {
-        if (attachment != null)
-        {
-            final String fileName = attachment.getFileName();
-            if (fileName != null && fileName.startsWith(HIDDEN_EXPERIMENT_PROPERTY_PREFIX2))
-            {
-                attachment.setFileName(fileName.substring(1));
-            }
-        }
-    }
-
-    public final static boolean isHiddenFile(final String fileName)
-    {
-        return fileName.startsWith(HIDDEN_EXPERIMENT_PROPERTY_PREFIX)
-                && (fileName.length() == 1 || fileName.charAt(1) != HIDDEN_EXPERIMENT_PROPERTY_PREFIX_CHARACTER);
-    }
-
-    public final static String escapeFileName(final String fileName)
-    {
-        if (fileName != null && fileName.startsWith(HIDDEN_EXPERIMENT_PROPERTY_PREFIX))
-        {
-            return HIDDEN_EXPERIMENT_PROPERTY_PREFIX + fileName;
-        }
-        return fileName;
-    }
-
     //
     // IMatchingEntity
     //
@@ -579,11 +492,6 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
         return EntityKind.EXPERIMENT;
     }
 
-    public void ensureAttachmentsLoaded()
-    {
-        HibernateUtils.initialize(getInternalAttachments());
-    }
-
     @Version
     @Column(name = ColumnNames.MODIFICATION_TIMESTAMP_COLUMN, nullable = false)
     public Date getModificationDate()
@@ -595,4 +503,12 @@ public class ExperimentPE implements IEntityPropertiesHolder<ExperimentPropertyP
     {
         this.modificationDate = versionDate;
     }
+
+    @Override
+    @Transient
+    public String getHolderName()
+    {
+        return "experiment";
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java
index 57bcc2c9e99..31539fa8f91 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ProjectPE.java
@@ -17,7 +17,10 @@
 package ch.systemsx.cisd.openbis.generic.shared.dto;
 
 import java.io.Serializable;
+import java.util.Date;
+import java.util.Set;
 
+import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -26,6 +29,7 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
 import javax.persistence.SequenceGenerator;
 import javax.persistence.Table;
 import javax.persistence.Transient;
@@ -34,6 +38,8 @@ import javax.persistence.UniqueConstraint;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
 import org.apache.commons.lang.builder.ToStringBuilder;
+import org.hibernate.annotations.Generated;
+import org.hibernate.annotations.GenerationTime;
 import org.hibernate.search.annotations.Field;
 import org.hibernate.search.annotations.Index;
 import org.hibernate.search.annotations.IndexedEmbedded;
@@ -42,6 +48,7 @@ import org.hibernate.validator.Length;
 import org.hibernate.validator.NotNull;
 import org.hibernate.validator.Pattern;
 
+import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.common.utilities.ModifiedShortPrefixToStringStyle;
 import ch.systemsx.cisd.openbis.generic.shared.GenericSharedConstants;
 import ch.systemsx.cisd.openbis.generic.shared.dto.hibernate.SearchFieldConstants;
@@ -56,8 +63,8 @@ import ch.systemsx.cisd.openbis.generic.shared.util.EqualsHashUtils;
 @Table(name = TableNames.PROJECTS_TABLE, uniqueConstraints =
     { @UniqueConstraint(columnNames =
         { ColumnNames.CODE_COLUMN, ColumnNames.GROUP_COLUMN }) })
-public final class ProjectPE extends HibernateAbstractRegistrationHolder implements
-        Comparable<ProjectPE>, IIdAndCodeHolder, Serializable
+public final class ProjectPE extends AttachmentHolderPE implements Comparable<ProjectPE>,
+        IIdAndCodeHolder, Serializable
 {
     public static final ProjectPE[] EMPTY_ARRAY = new ProjectPE[0];
 
@@ -78,6 +85,34 @@ public final class ProjectPE extends HibernateAbstractRegistrationHolder impleme
 
     private DataStorePE dataStore;
 
+    private PersonPE registrator;
+
+    private Date registrationDate;
+
+    @Column(name = ColumnNames.REGISTRATION_TIMESTAMP_COLUMN, nullable = false, insertable = false, updatable = false)
+    @Generated(GenerationTime.INSERT)
+    public Date getRegistrationDate()
+    {
+        return HibernateAbstractRegistrationHolder.getDate(registrationDate);
+    }
+
+    public void setRegistrationDate(final Date registrationDate)
+    {
+        this.registrationDate = registrationDate;
+    }
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    @JoinColumn(name = ColumnNames.PERSON_REGISTERER_COLUMN, updatable = false)
+    public PersonPE getRegistrator()
+    {
+        return registrator;
+    }
+
+    public void setRegistrator(final PersonPE registrator)
+    {
+        this.registrator = registrator;
+    }
+
     @ManyToOne(fetch = FetchType.EAGER)
     @JoinColumn(name = ColumnNames.DATA_STORE_COLUMN, updatable = false)
     public final DataStorePE getDataStore()
@@ -232,4 +267,22 @@ public final class ProjectPE extends HibernateAbstractRegistrationHolder impleme
     {
         return code;
     }
+
+    @Override
+    @Transient
+    public String getHolderName()
+    {
+        return "project";
+    }
+
+    @Override
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "projectParentInternal")
+    @IndexedEmbedded(prefix = SearchFieldConstants.PREFIX_PROJECT_ATTACHMENTS)
+    @Private
+    // for Hibernate and bean conversion only
+    public Set<AttachmentPE> getInternalAttachments()
+    {
+        return attachments;
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java
index 4910dc9a88c..1cb612cf395 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SamplePE.java
@@ -58,6 +58,7 @@ import org.hibernate.validator.Length;
 import org.hibernate.validator.NotNull;
 import org.hibernate.validator.Pattern;
 
+import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.common.collections.UnmodifiableSetDecorator;
 import ch.systemsx.cisd.common.utilities.ModifiedShortPrefixToStringStyle;
 import ch.systemsx.cisd.openbis.generic.shared.GenericSharedConstants;
@@ -78,7 +79,7 @@ import ch.systemsx.cisd.openbis.generic.shared.util.EqualsHashUtils;
         + ColumnNames.GROUP_COLUMN + " IS NULL) OR (" + ColumnNames.DATABASE_INSTANCE_COLUMN
         + " IS NULL AND " + ColumnNames.GROUP_COLUMN + " IS NOT NULL)")
 @Indexed
-public class SamplePE implements IIdAndCodeHolder, Comparable<SamplePE>,
+public class SamplePE extends AttachmentHolderPE implements IIdAndCodeHolder, Comparable<SamplePE>,
         IEntityPropertiesHolder<SamplePropertyPE>, IMatchingEntity, Serializable
 {
     private static final long serialVersionUID = GenericSharedConstants.VERSION;
@@ -534,4 +535,22 @@ public class SamplePE implements IIdAndCodeHolder, Comparable<SamplePE>,
     {
         return EntityKind.SAMPLE;
     }
+
+    @Override
+    @Transient
+    public String getHolderName()
+    {
+        return "sample";
+    }
+
+    @Override
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "sampleParentInternal")
+    @IndexedEmbedded(prefix = SearchFieldConstants.PREFIX_SAMPLE_ATTACHMENTS)
+    @Private
+    // for Hibernate and bean conversion only
+    public Set<AttachmentPE> getInternalAttachments()
+    {
+        return attachments;
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ValidationMessages.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ValidationMessages.java
index cd9d31f1417..c1ba7942199 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ValidationMessages.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ValidationMessages.java
@@ -25,156 +25,136 @@ package ch.systemsx.cisd.openbis.generic.shared.dto;
  * 
  * @author Christian Ribeaud
  */
-public final class ValidationMessages {
+public final class ValidationMessages
+{
 
-	private static final String CAN_NOT_BE_NULL = " can not be null.";
+    private static final String CAN_NOT_BE_NULL = " can not be null.";
 
-	private static final String LENGTH_PREFIX = "Given ";
+    private static final String LENGTH_PREFIX = "Given ";
 
-	private static final String LENGTH_SUFFIX = " '%s' is too long (maximal length: {max} characters).";
+    private static final String LENGTH_SUFFIX =
+            " '%s' is too long (maximal length: {max} characters).";
 
-	public static final String ATTACHMENT_CONTENT_NOT_NULL_MESSAGE = "The content of the attachment"
-			+ CAN_NOT_BE_NULL;
+    public static final String ATTACHMENT_CONTENT_NOT_NULL_MESSAGE =
+            "The content of the attachment" + CAN_NOT_BE_NULL;
 
-	public static final String CODE_LENGTH_MESSAGE = "Given code '%s' is either too short (minimal length: {min} character) "
-			+ "or too long (maximal length: {max} characters).";
+    public static final String CODE_LENGTH_MESSAGE =
+            "Given code '%s' is either too short (minimal length: {min} character) "
+                    + "or too long (maximal length: {max} characters).";
 
-	public static final String CODE_NOT_NULL_MESSAGE = "Code" + CAN_NOT_BE_NULL;
+    public static final String CODE_NOT_NULL_MESSAGE = "Code" + CAN_NOT_BE_NULL;
 
-	public static final String CODE_PATTERN_MESSAGE = "Given code '%s' contains illegal characters (allowed: A-Z, a-z, 0-9, _ and -)";
+    public static final String CODE_PATTERN_MESSAGE =
+            "Given code '%s' contains illegal characters (allowed: A-Z, a-z, 0-9, _ and -)";
 
-	public static final String DATA_SET_TYPE_NOT_NULL_MESSAGE = "Data set type"
-			+ CAN_NOT_BE_NULL;
+    public static final String DATA_SET_TYPE_NOT_NULL_MESSAGE = "Data set type" + CAN_NOT_BE_NULL;
 
-	public static final String DATA_TYPE_NOT_NULL_MESSAGE = "Data type"
-			+ CAN_NOT_BE_NULL;
+    public static final String DATA_TYPE_NOT_NULL_MESSAGE = "Data type" + CAN_NOT_BE_NULL;
 
-	public static final String DATABASE_INSTANCE_NOT_NULL_MESSAGE = "Database instance"
-			+ CAN_NOT_BE_NULL;
+    public static final String DATABASE_INSTANCE_NOT_NULL_MESSAGE =
+            "Database instance" + CAN_NOT_BE_NULL;
 
-	public final static String DESCRIPTION_LENGTH_MESSAGE = LENGTH_PREFIX
-			+ "description" + LENGTH_SUFFIX;
+    public final static String DESCRIPTION_LENGTH_MESSAGE =
+            LENGTH_PREFIX + "description" + LENGTH_SUFFIX;
 
-	public static final String DESCRIPTION_NOT_NULL_MESSAGE = "Description"
-			+ CAN_NOT_BE_NULL;
+    public static final String DESCRIPTION_NOT_NULL_MESSAGE = "Description" + CAN_NOT_BE_NULL;
 
-	public static final String DOWNLOAD_URL_NOT_NULL_MESSAGE = "Download URL"
-			+ CAN_NOT_BE_NULL;
+    public static final String DOWNLOAD_URL_NOT_NULL_MESSAGE = "Download URL" + CAN_NOT_BE_NULL;
 
-	public static final String EMAIL_EMAIL_MESSAGE = "Given email address '%s' is not a valid one.";
+    public static final String EMAIL_EMAIL_MESSAGE = "Given email address '%s' is not a valid one.";
 
-	public static final String EMAIL_LENGTH_MESSAGE = LENGTH_PREFIX
-			+ "email address" + LENGTH_SUFFIX;
+    public static final String EMAIL_LENGTH_MESSAGE =
+            LENGTH_PREFIX + "email address" + LENGTH_SUFFIX;
 
-	public static final String EXPERIMENT_NOT_NULL_MESSAGE = "Experiment "
-			+ CAN_NOT_BE_NULL;
+    public static final String EXPERIMENT_NOT_NULL_MESSAGE = "Experiment " + CAN_NOT_BE_NULL;
 
-	public static final String EXPERIMENT_TYPE_NOT_NULL_MESSAGE = "Experiment type"
-			+ CAN_NOT_BE_NULL;
+    public static final String EXPERIMENT_TYPE_NOT_NULL_MESSAGE =
+            "Experiment type" + CAN_NOT_BE_NULL;
 
-	public static final String EXPERIMENT_TYPE_PROPERTY_TYPE_NOT_NULL_MESSAGE = "Experiment type - property type"
-			+ CAN_NOT_BE_NULL;
+    public static final String EXPERIMENT_TYPE_PROPERTY_TYPE_NOT_NULL_MESSAGE =
+            "Experiment type - property type" + CAN_NOT_BE_NULL;
 
-	public static final String FILE_FORMAT_TYPE_NOT_NULL_MESSAGE = "File format type"
-			+ CAN_NOT_BE_NULL;
+    public static final String FILE_FORMAT_TYPE_NOT_NULL_MESSAGE =
+            "File format type" + CAN_NOT_BE_NULL;
 
-	public static final String FILE_NAME_LENGTH_MESSAGE = LENGTH_PREFIX
-			+ "file name" + LENGTH_SUFFIX;
+    public static final String FILE_NAME_LENGTH_MESSAGE =
+            LENGTH_PREFIX + "file name" + LENGTH_SUFFIX;
 
-	public static final String FILE_NAME_NOT_NULL_MESSAGE = "File name"
-			+ CAN_NOT_BE_NULL;
+    public static final String FILE_NAME_NOT_NULL_MESSAGE = "File name" + CAN_NOT_BE_NULL;
 
-	public static final String FIRST_NAME_LENGTH_MESSAGE = LENGTH_PREFIX
-			+ "first name" + LENGTH_SUFFIX;
+    public static final String FIRST_NAME_LENGTH_MESSAGE =
+            LENGTH_PREFIX + "first name" + LENGTH_SUFFIX;
 
-	public static final String GROUP_NOT_NULL_MESSAGE = "Group"
-			+ CAN_NOT_BE_NULL;
+    public static final String GROUP_NOT_NULL_MESSAGE = "Group" + CAN_NOT_BE_NULL;
 
-	public static final String IS_COMPLETE_NOT_NULL_MESSAGE = "Complete flag"
-			+ CAN_NOT_BE_NULL;
+    public static final String IS_COMPLETE_NOT_NULL_MESSAGE = "Complete flag" + CAN_NOT_BE_NULL;
 
-	public static final String LABEL_LENGTH_MESSAGE = LENGTH_PREFIX + "label"
-			+ LENGTH_SUFFIX;
+    public static final String LABEL_LENGTH_MESSAGE = LENGTH_PREFIX + "label" + LENGTH_SUFFIX;
 
-	public static final String LABEL_NOT_NULL_MESSAGE = "Label"
-			+ CAN_NOT_BE_NULL;
+    public static final String LABEL_NOT_NULL_MESSAGE = "Label" + CAN_NOT_BE_NULL;
 
-	public static final String LAST_NAME_LENGTH_MESSAGE = LENGTH_PREFIX
-			+ "last name" + LENGTH_SUFFIX;
+    public static final String LAST_NAME_LENGTH_MESSAGE =
+            LENGTH_PREFIX + "last name" + LENGTH_SUFFIX;
 
-	public static final String LOCATION_LENGTH_MESSAGE = LENGTH_PREFIX
-			+ "location" + LENGTH_SUFFIX;
+    public static final String LOCATION_LENGTH_MESSAGE = LENGTH_PREFIX + "location" + LENGTH_SUFFIX;
 
-	public static final String LOCATION_NOT_NULL_MESSAGE = "Location"
-			+ CAN_NOT_BE_NULL;
+    public static final String LOCATION_NOT_NULL_MESSAGE = "Location" + CAN_NOT_BE_NULL;
 
-	public static final String LOCATION_NOT_RELATIVE = "Location is not relative";
+    public static final String LOCATION_NOT_RELATIVE = "Location is not relative";
 
-	public static final String CODE_NOT_USER_NAMESPACE = "Code does not contain '"
-			+ CodeConverter.USER_PROPERTY_PREFIX + "' prefix.";
+    public static final String CODE_NOT_USER_NAMESPACE =
+            "Code does not contain '" + CodeConverter.USER_PROPERTY_PREFIX + "' prefix.";
 
-	public static final String LOCATOR_TYPE_NOT_NULL_MESSAGE = "Locator type"
-			+ CAN_NOT_BE_NULL;
+    public static final String LOCATOR_TYPE_NOT_NULL_MESSAGE = "Locator type" + CAN_NOT_BE_NULL;
 
-	public static final String MATERIAL_NOT_NULL_MESSAGE = "Material"
-			+ CAN_NOT_BE_NULL;
+    public static final String MATERIAL_NOT_NULL_MESSAGE = "Material" + CAN_NOT_BE_NULL;
 
-	public static final String MATERIAL_TYPE_NOT_NULL_MESSAGE = "Material type"
-			+ CAN_NOT_BE_NULL;
+    public static final String MATERIAL_TYPE_NOT_NULL_MESSAGE = "Material type" + CAN_NOT_BE_NULL;
 
-	public static final String MATERIAL_TYPE_PROPERTY_TYPE_NOT_NULL_MESSAGE = "Material type - property type"
-			+ CAN_NOT_BE_NULL;
+    public static final String MATERIAL_TYPE_PROPERTY_TYPE_NOT_NULL_MESSAGE =
+            "Material type - property type" + CAN_NOT_BE_NULL;
 
-	public static final String PERSON_NOT_NULL_MESSAGE = "Person"
-			+ CAN_NOT_BE_NULL;
+    public static final String PERSON_NOT_NULL_MESSAGE = "Person" + CAN_NOT_BE_NULL;
 
-	public static final String PROJECT_NOT_NULL_MESSAGE = "Project"
-			+ CAN_NOT_BE_NULL;
+    public static final String PROJECT_NOT_NULL_MESSAGE = "Project" + CAN_NOT_BE_NULL;
 
-	public static final String PROPERTY_TYPE_NOT_NULL_MESSAGE = "Property type"
-			+ CAN_NOT_BE_NULL;
+    public static final String SAMPLE_NOT_NULL_MESSAGE = "Sample" + CAN_NOT_BE_NULL;
 
-	public static final String ROLE_NOT_NULL_MESSAGE = "Role" + CAN_NOT_BE_NULL;
+    public static final String PROPERTY_TYPE_NOT_NULL_MESSAGE = "Property type" + CAN_NOT_BE_NULL;
 
-	public static final String SAMPLE_TYPE_NOT_NULL_MESSAGE = "Sample type"
-			+ CAN_NOT_BE_NULL;
+    public static final String ROLE_NOT_NULL_MESSAGE = "Role" + CAN_NOT_BE_NULL;
 
-	public static final String SAMPLE_TYPE_PROPERTY_TYPE_NOT_NULL_MESSAGE = "Sample type - property type"
-			+ CAN_NOT_BE_NULL;
+    public static final String SAMPLE_TYPE_NOT_NULL_MESSAGE = "Sample type" + CAN_NOT_BE_NULL;
 
-	public static final String STORAGE_FORMAT_NOT_NULL_MESSAGE = "Storage format"
-			+ CAN_NOT_BE_NULL;
+    public static final String SAMPLE_TYPE_PROPERTY_TYPE_NOT_NULL_MESSAGE =
+            "Sample type - property type" + CAN_NOT_BE_NULL;
 
-	public static final String USER_ID_LENGTH_MESSAGE = LENGTH_PREFIX
-			+ "user id" + LENGTH_SUFFIX;
+    public static final String STORAGE_FORMAT_NOT_NULL_MESSAGE = "Storage format" + CAN_NOT_BE_NULL;
 
-	public static final String USER_ID_NOT_NULL_MESSAGE = "User id"
-			+ CAN_NOT_BE_NULL;
+    public static final String USER_ID_LENGTH_MESSAGE = LENGTH_PREFIX + "user id" + LENGTH_SUFFIX;
 
-	public static final String UUID_NOT_NULL_MESSAGE = "UUID" + CAN_NOT_BE_NULL;
+    public static final String USER_ID_NOT_NULL_MESSAGE = "User id" + CAN_NOT_BE_NULL;
 
-	public static final String VALUE_LENGTH_MESSAGE = LENGTH_PREFIX + "value"
-			+ LENGTH_SUFFIX;
+    public static final String UUID_NOT_NULL_MESSAGE = "UUID" + CAN_NOT_BE_NULL;
 
-	public static final String VALUE_NOT_NULL_MESSAGE = "Value"
-			+ CAN_NOT_BE_NULL;
+    public static final String VALUE_LENGTH_MESSAGE = LENGTH_PREFIX + "value" + LENGTH_SUFFIX;
 
-	public static final String VERSION_NOT_NULL_MESSAGE = "Version"
-			+ CAN_NOT_BE_NULL;
+    public static final String VALUE_NOT_NULL_MESSAGE = "Value" + CAN_NOT_BE_NULL;
 
-	public static final String VOCABULARY_NOT_NULL_MESSAGE = "Vocabulary"
-			+ CAN_NOT_BE_NULL;
+    public static final String VERSION_NOT_NULL_MESSAGE = "Version" + CAN_NOT_BE_NULL;
 
-	public static final String VALID_USER_CODE_DESCRIPTION = "User code must not be empty and must contain only allowed characters: "
-			+ "letters, digits, '_', '.', '-', '@'. Note that whitespaces are not allowed.";
+    public static final String VOCABULARY_NOT_NULL_MESSAGE = "Vocabulary" + CAN_NOT_BE_NULL;
 
-	public static final String EVENT_TYPE_NOT_NULL_MESSAGE = "Event Type"
-			+ CAN_NOT_BE_NULL;
+    public static final String VALID_USER_CODE_DESCRIPTION =
+            "User code must not be empty and must contain only allowed characters: "
+                    + "letters, digits, '_', '.', '-', '@'. Note that whitespaces are not allowed.";
 
-	public static final String DATA_NOT_NULL_MESSAGE = "Data "
-			+ CAN_NOT_BE_NULL;
+    public static final String EVENT_TYPE_NOT_NULL_MESSAGE = "Event Type" + CAN_NOT_BE_NULL;
 
-	private ValidationMessages() {
-		// Can not be instantiated.
-	}
+    public static final String DATA_NOT_NULL_MESSAGE = "Data " + CAN_NOT_BE_NULL;
+
+    private ValidationMessages()
+    {
+        // Can not be instantiated.
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/hibernate/SearchFieldConstants.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/hibernate/SearchFieldConstants.java
index 6c2b77032a0..0b9d1d1e63a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/hibernate/SearchFieldConstants.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/hibernate/SearchFieldConstants.java
@@ -69,4 +69,8 @@ public final class SearchFieldConstants
      */
 
     public static final String PREFIX_EXPERIMENT_ATTACHMENTS = "";
+
+    public static final String PREFIX_SAMPLE_ATTACHMENTS = "";
+
+    public static final String PREFIX_PROJECT_ATTACHMENTS = "";
 }
diff --git a/openbis/source/java/hibernateContext.xml b/openbis/source/java/hibernateContext.xml
index 812199d155a..ddb69cdf12b 100644
--- a/openbis/source/java/hibernateContext.xml
+++ b/openbis/source/java/hibernateContext.xml
@@ -71,6 +71,8 @@
                 
                 <value>ch.systemsx.cisd.openbis.generic.shared.dto.DataSetPropertyPE</value>
                 <value>ch.systemsx.cisd.openbis.generic.shared.dto.DataSetTypePropertyTypePE</value>
+                
+                <value>ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentHolderPE</value>
             </list>
         </property>
         <!-- 
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 e3ec351fc80..d3d00f57dce 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
@@ -193,7 +193,7 @@ public class ETLServiceTest extends AbstractServerTestCase
         context.checking(new Expectations()
             {
                 {
-                    one(experimentAttachmentDAO).listExperimentAttachments(experiment);
+                    one(experimentAttachmentDAO).listAttachments(experiment);
                     will(returnValue(Collections.emptyList()));
                 }
             });
@@ -217,7 +217,7 @@ public class ETLServiceTest extends AbstractServerTestCase
         context.checking(new Expectations()
             {
                 {
-                    one(experimentAttachmentDAO).listExperimentAttachments(experiment);
+                    one(experimentAttachmentDAO).listAttachments(experiment);
                     AttachmentPE attachment1 = new AttachmentPE();
                     attachment1.setFileName("blabla");
                     String code = "pCode";
@@ -226,21 +226,21 @@ public class ETLServiceTest extends AbstractServerTestCase
                                     "myPath");
                     will(returnValue(Arrays.asList(attachment1, processingPath)));
                     
-                    one(experimentAttachmentDAO).tryFindExpAttachmentByExpAndFileName(experiment,
+                    one(experimentAttachmentDAO).tryFindAttachmentByOwnerAndFileName(experiment,
                             processingPath.getFileName());
                     will(returnValue(processingPath));
                     
                     AttachmentPE processingDescription =
                         createProcessingInstruction(ETLService.PROCESSING_DESCRIPTION_TEMPLATE,
                                 code, "myDescription");
-                    one(experimentAttachmentDAO).tryFindExpAttachmentByExpAndFileName(experiment,
+                    one(experimentAttachmentDAO).tryFindAttachmentByOwnerAndFileName(experiment,
                             processingDescription.getFileName());
                     will(returnValue(processingDescription));
                     
                     AttachmentPE processingParameters =
                         createProcessingInstruction(ETLService.PROCESSING_PARAMETERS_TEMPLATE,
                                 code, "myParameters");
-                    one(experimentAttachmentDAO).tryFindExpAttachmentByExpAndFileName(experiment,
+                    one(experimentAttachmentDAO).tryFindAttachmentByOwnerAndFileName(experiment,
                             processingParameters.getFileName());
                     will(returnValue(processingParameters));
                 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerTestCase.java
index 2eeaf343add..9d6d2713fc0 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerTestCase.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerTestCase.java
@@ -43,7 +43,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IVocabularyBO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDatabaseInstanceDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityTypeDAO;
-import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExperimentAttachmentDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAttachmentDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IExternalDataDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IGroupDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IPersonDAO;
@@ -132,7 +132,7 @@ public abstract class AbstractServerTestCase extends AssertJUnit
 
     protected IEntityTypePropertyTypeBO entityTypePropertyTypeBO;
 
-    protected IExperimentAttachmentDAO experimentAttachmentDAO;
+    protected IAttachmentDAO experimentAttachmentDAO;
 
     protected IExternalDataBO externalDataBO;
 
@@ -154,7 +154,7 @@ public abstract class AbstractServerTestCase extends AssertJUnit
         roleAssignmentDAO = context.mock(IRoleAssignmentDAO.class);
         externalDataDAO = context.mock(IExternalDataDAO.class);
         entityTypeDAO = context.mock(IEntityTypeDAO.class);
-        experimentAttachmentDAO = context.mock(IExperimentAttachmentDAO.class);
+        experimentAttachmentDAO = context.mock(IAttachmentDAO.class);
         projectDAO = context.mock(IProjectDAO.class);
         sampleTypeDAO = context.mock(ISampleTypeDAO.class);
         propertyTypeDAO = context.mock(IPropertyTypeDAO.class);
@@ -193,7 +193,7 @@ public abstract class AbstractServerTestCase extends AssertJUnit
                     will(returnValue(sampleTypeDAO));
                     allowing(daoFactory).getExternalDataDAO();
                     will(returnValue(externalDataDAO));
-                    allowing(daoFactory).getExperimentAttachmentDAO();
+                    allowing(daoFactory).getAttachmentDAO();
                     will(returnValue(experimentAttachmentDAO));
                 }
             });
-- 
GitLab