From 191924760a1997f78f067508126279e74fad30d5 Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Mon, 4 Mar 2013 08:15:53 +0000
Subject: [PATCH] SP-524, BIS-278: Implemented and partially tested

SVN: 28500
---
 .../client/application/GenericConstants.java  |  18 +-
 .../attachment/AttachmentDownloadHelper.java  |  11 +-
 .../client/web/server/AbstractServlet.java    |   6 +
 .../api/v1/GeneralInformationService.java     | 200 ++++-----
 .../v1/GeneralInformationServiceLogger.java   |  59 +--
 .../predicate/ExperimentIdPredicate.java      |  75 ++++
 .../predicate/ProjectIdPredicate.java         |  73 ++++
 .../predicate/SampleIdPredicate.java          |  75 ++++
 .../server/business/bo/IProjectBO.java        |   7 +
 .../generic/server/business/bo/ProjectBO.java |  38 ++
 .../generic/shared/api/v1/Translator.java     | 104 +++--
 .../basic/AttachmentDownloadConstants.java    |  41 ++
 .../web/server/AttachmentDownloadServlet.java |  57 ++-
 .../server/AttachmentDownloadServletTest.java |  19 +-
 .../api/v1/GeneralInformationServiceTest.java | 389 +++++++++---------
 .../api/v1/IGeneralInformationService.java    |  33 ++
 .../generic/shared/api/v1/dto/Attachment.java | 196 +++------
 .../api/v1/dto/id/project/IProjectId.java     |  31 ++
 .../dto/id/project/ProjectIdentifierId.java   |  52 +++
 .../v1/dto/id/project/ProjectPermIdId.java    |  53 +++
 .../v1/dto/id/project/ProjectTechIdId.java    |  53 +++
 21 files changed, 1039 insertions(+), 551 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ExperimentIdPredicate.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ProjectIdPredicate.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/SampleIdPredicate.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/AttachmentDownloadConstants.java
 create mode 100644 openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/IProjectId.java
 create mode 100644 openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectIdentifierId.java
 create mode 100644 openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectPermIdId.java
 create mode 100644 openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectTechIdId.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/GenericConstants.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/GenericConstants.java
index 35ca50db76e..88a5279f520 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/GenericConstants.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/GenericConstants.java
@@ -18,6 +18,8 @@ package ch.systemsx.cisd.openbis.generic.client.web.client.application;
 
 import com.google.gwt.core.client.GWT;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.AttachmentDownloadConstants;
+
 /**
  * Some generic constants.
  * 
@@ -54,16 +56,7 @@ public final class GenericConstants
 
     /** Name of the servlet to download an experiment attachment. */
     public static final String ATTACHMENT_DOWNLOAD_SERVLET_NAME =
-            createServicePath("attachment-download");
-
-    /** The HTTP URL parameter used to specify the version. */
-    public static final String VERSION_PARAMETER = "version";
-
-    /** The HTTP URL parameter used to specify the file name. */
-    public static final String FILE_NAME_PARAMETER = "fileName";
-
-    /** The HTTP URL parameter used to specify the attachment holder. */
-    public static final String ATTACHMENT_HOLDER_PARAMETER = "attachmentHolder";
+            createServicePath(AttachmentDownloadConstants.ATTACHMENT_DOWNLOAD_SERVLET_NAME);
 
     /** Name of the servlet to export and download a file. */
     public static final String FILE_EXPORTER_DOWNLOAD_SERVLET_NAME =
@@ -74,9 +67,6 @@ public final class GenericConstants
 
     public static final String LABEL_SEPARATOR = ":";
 
-    /** The HTTP URL parameter used to specify the technical id. */
-    public static final String TECH_ID_PARAMETER = "id";
-
     /** Name of the servlet to download a template. */
     public static final String TEMPLATE_SERVLET_NAME = createServicePath("template-download");
 
@@ -109,7 +99,7 @@ public final class GenericConstants
     public static final String WITH_EXPERIMENTS = "with_experiments";
 
     public static final String WITH_SPACE = "with_space";
-    
+
     public static final String BATCH_OPERATION_KIND = "batch_operation_kind";
 
     public static final String ITEMS_TEXTAREA_REGEX = "\n|\r\n|, *";
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/attachment/AttachmentDownloadHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/attachment/AttachmentDownloadHelper.java
index 7f520256164..fefdb9cd075 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/attachment/AttachmentDownloadHelper.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/attachment/AttachmentDownloadHelper.java
@@ -18,6 +18,7 @@ package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.attach
 
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericConstants;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.WindowUtils;
+import ch.systemsx.cisd.openbis.generic.shared.basic.AttachmentDownloadConstants;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IAttachmentHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.URLMethodWithParameters;
 
@@ -40,13 +41,15 @@ public class AttachmentDownloadHelper
                 new URLMethodWithParameters(GenericConstants.ATTACHMENT_DOWNLOAD_SERVLET_NAME);
         if (version != null)
         {
-            methodWithParameters.addParameter(GenericConstants.VERSION_PARAMETER, version);
+            methodWithParameters.addParameter(AttachmentDownloadConstants.VERSION_PARAMETER,
+                    version);
         }
-        methodWithParameters.addParameter(GenericConstants.FILE_NAME_PARAMETER, fileName);
-        methodWithParameters.addParameter(GenericConstants.ATTACHMENT_HOLDER_PARAMETER,
+        methodWithParameters
+                .addParameter(AttachmentDownloadConstants.FILE_NAME_PARAMETER, fileName);
+        methodWithParameters.addParameter(AttachmentDownloadConstants.ATTACHMENT_HOLDER_PARAMETER,
                 attachmentHolder.getAttachmentHolderKind().name());
         // NOTE: this exp.getId() could be null if exp is a proxy
-        methodWithParameters.addParameter(GenericConstants.TECH_ID_PARAMETER,
+        methodWithParameters.addParameter(AttachmentDownloadConstants.TECH_ID_PARAMETER,
                 attachmentHolder.getId());
         return methodWithParameters.toString();
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractServlet.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractServlet.java
index 01abcfc43e5..64eb01fd8fa 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractServlet.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractServlet.java
@@ -28,6 +28,7 @@ import org.springframework.web.servlet.mvc.AbstractController;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.server.SessionConstants;
+import ch.systemsx.cisd.openbis.generic.shared.basic.GenericSharedConstants;
 
 /**
  * @author Franz-Josef Elmer
@@ -36,6 +37,11 @@ public abstract class AbstractServlet extends AbstractController
 {
     protected final String getSessionToken(final HttpServletRequest request)
     {
+        String sessionToken = request.getParameter(GenericSharedConstants.SESSION_ID_PARAMETER);
+        if (sessionToken != null)
+        {
+            return sessionToken;
+        }
         return getParameter(request, SessionConstants.OPENBIS_SESSION_TOKEN_ATTRIBUTE_KEY);
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
index 96673c5cb73..82390d12162 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
@@ -44,8 +44,11 @@ import ch.systemsx.cisd.openbis.generic.server.authorization.annotation.Capabili
 import ch.systemsx.cisd.openbis.generic.server.authorization.annotation.ReturnValueFilter;
 import ch.systemsx.cisd.openbis.generic.server.authorization.annotation.RolesAllowed;
 import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.ExperimentAugmentedCodePredicate;
+import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.ExperimentIdPredicate;
 import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.ExperimentListPredicate;
+import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.ProjectIdPredicate;
 import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.ProjectPredicate;
+import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.SampleIdPredicate;
 import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.SampleListPredicate;
 import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.SamplePredicate;
 import ch.systemsx.cisd.openbis.generic.server.authorization.validator.DataSetByExperimentIdentifierValidator;
@@ -55,6 +58,9 @@ import ch.systemsx.cisd.openbis.generic.server.authorization.validator.SampleByI
 import ch.systemsx.cisd.openbis.generic.server.authorization.validator.SimpleSpaceValidator;
 import ch.systemsx.cisd.openbis.generic.server.business.IPropertiesBatchManager;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ICommonBusinessObjectFactory;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.IExperimentBO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.IProjectBO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.fetchoptions.datasetlister.DataSetLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.fetchoptions.datasetlister.IDataSetLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.fetchoptions.samplelister.ISampleLister;
@@ -66,6 +72,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDatabaseInstanceDAO;
 import ch.systemsx.cisd.openbis.generic.shared.ICommonServer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.Translator;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
@@ -85,7 +92,11 @@ 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.api.v1.dto.SpaceWithProjectsAndRoleAssignments;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Vocabulary;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.IObjectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.IExperimentId;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.metaproject.IMetaprojectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.IProjectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.ISampleId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetRelatedEntities;
@@ -96,13 +107,17 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListMaterialCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Metaproject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
+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.AuthorizationGroupPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.MetaprojectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.RoleAssignmentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
@@ -1086,127 +1101,66 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
         return result;
     }
 
-    // @Override
-    // @Transactional(readOnly = true)
-    // @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    // public List<Attachment> listAttachmentsForProject(String sessionToken,
-    // @AuthorizationGuard(guardClass = ProjectIdentifierPredicate.class)
-    // ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ProjectIdentifier project,
-    // boolean allVersions)
-    // {
-    // final TechId techId;
-    // if (project.getDatabaseId() != null)
-    // {
-    // techId = new TechId(project.getDatabaseId());
-    // } else if (project.getPermId() != null)
-    // {
-    // final Long id =
-    // boFactory.getEntityResolver().tryResolveProjectIdByPermId(
-    // project.getPermId());
-    // if (id == null)
-    // {
-    // return Collections.emptyList();
-    // }
-    // techId = new TechId(id);
-    // } else if (project.getCode() != null)
-    // {
-    // final Long id =
-    // boFactory.getEntityResolver().tryResolveProjectIdByCode(
-    // project.getSpaceCode(), project.getCode());
-    // if (id == null)
-    // {
-    // return Collections.emptyList();
-    // }
-    // techId = new TechId(id);
-    // } else
-    // {
-    // throw new IllegalArgumentException("No identifier given.");
-    // }
-    // final List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments =
-    // commonServer.listProjectAttachments(sessionToken, techId);
-    // return Translator.translateAttachments(attachments, allVersions);
-    // }
-    //
-    // @Override
-    // @Transactional(readOnly = true)
-    // @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    // public List<Attachment> listAttachmentsForExperiment(String sessionToken,
-    // @AuthorizationGuard(guardClass = ExperimentIdentifierPredicate.class)
-    // ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ExperimentIdentifier experiment,
-    // boolean allVersions)
-    // {
-    // final TechId techId;
-    // if (experiment.getDatabaseId() != null)
-    // {
-    // techId = new TechId(experiment.getDatabaseId());
-    // } else if (experiment.getPermId() != null)
-    // {
-    // final Long id =
-    // boFactory.getEntityResolver().tryResolveExperimentIdByPermId(
-    // experiment.getPermId());
-    // if (id == null)
-    // {
-    // return Collections.emptyList();
-    // }
-    // techId = new TechId(id);
-    // } else if (experiment.getCode() != null)
-    // {
-    // final Long id =
-    // boFactory.getEntityResolver().tryResolveExperimentIdByCode(
-    // experiment.getSpaceCode(), experiment.getProjectCode(),
-    // experiment.getCode());
-    // if (id == null)
-    // {
-    // return Collections.emptyList();
-    // }
-    // techId = new TechId(id);
-    // } else
-    // {
-    // throw new IllegalArgumentException("No identifier given.");
-    // }
-    // final List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments =
-    // commonServer.listExperimentAttachments(sessionToken, techId);
-    // return Translator.translateAttachments(attachments, allVersions);
-    // }
-    //
-    // @Override
-    // @Transactional(readOnly = true)
-    // @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    // public List<Attachment> listAttachmentsForSample(String sessionToken,
-    // @AuthorizationGuard(guardClass = SampleIdentifierPredicate.class)
-    // SampleIdentifier sample,
-    // boolean allVersions)
-    // {
-    // final TechId techId;
-    // if (sample.getDatabaseId() != null)
-    // {
-    // techId = new TechId(sample.getDatabaseId());
-    // } else if (sample.getPermId() != null)
-    // {
-    // final Long id =
-    // boFactory.getEntityResolver().tryResolveSampleIdByPermId(
-    // sample.getPermId());
-    // if (id == null)
-    // {
-    // return Collections.emptyList();
-    // }
-    // techId = new TechId(id);
-    // } else if (sample.getCode() != null)
-    // {
-    // final Long id =
-    // boFactory.getEntityResolver().tryResolveSampleIdByCode(
-    // sample.getSpaceCode(), sample.getCode());
-    // if (id == null)
-    // {
-    // return Collections.emptyList();
-    // }
-    // techId = new TechId(id);
-    // } else
-    // {
-    // throw new IllegalArgumentException("No identifier given.");
-    // }
-    // final List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments =
-    // commonServer.listSampleAttachments(sessionToken, techId);
-    // return Translator.translateAttachments(attachments, allVersions);
-    // }
+    @Override
+    @Transactional(readOnly = true)
+    @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
+    public List<Attachment> listAttachmentsForProject(String sessionToken,
+            @AuthorizationGuard(guardClass = ProjectIdPredicate.class)
+            IProjectId projectId, boolean allVersions)
+    {
+        Session session = getSession(sessionToken);
+
+        IProjectBO projectBO = boFactory.createProjectBO(session);
+        ProjectPE project = projectBO.tryFindByProjectId(projectId);
+        if (project == null)
+        {
+            throw new UserFailureException("No project found for id '" + projectId + "'.");
+        }
+        return listAttachments(sessionToken, projectId, project, allVersions);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
+    public List<Attachment> listAttachmentsForExperiment(String sessionToken,
+            @AuthorizationGuard(guardClass = ExperimentIdPredicate.class)
+            IExperimentId experimentId, boolean allVersions)
+    {
+        Session session = getSession(sessionToken);
+
+        IExperimentBO experimentBO = boFactory.createExperimentBO(session);
+        ExperimentPE experiment = experimentBO.tryFindByExperimentId(experimentId);
+        if (experiment == null)
+        {
+            throw new UserFailureException("No experiment found for id '" + experimentId + "'.");
+        }
+        return listAttachments(sessionToken, experimentId, experiment, allVersions);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
+    public List<Attachment> listAttachmentsForSample(String sessionToken,
+            @AuthorizationGuard(guardClass = SampleIdPredicate.class)
+            ISampleId sampleId, boolean allVersions)
+    {
+        Session session = getSession(sessionToken);
+
+        ISampleBO sampleBO = boFactory.createSampleBO(session);
+        SamplePE sample = sampleBO.tryFindBySampleId(sampleId);
+        if (sample == null)
+        {
+            throw new UserFailureException("No sample found for id '" + sampleId + "'.");
+        }
+        return listAttachments(sessionToken, sampleId, sample, allVersions);
+    }
+
+    private List<Attachment> listAttachments(String sessionToken, IObjectId objectId,
+            AttachmentHolderPE attachmentHolder, boolean allVersions)
+    {
+        List<AttachmentPE> attachments =
+                getDAOFactory().getAttachmentDAO().listAttachments(attachmentHolder);
+        return Translator.translateAttachments(sessionToken, objectId, attachmentHolder,
+                attachments, allVersions);
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
index 33789f567a6..099a51ab7ec 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
@@ -26,6 +26,7 @@ import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.openbis.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.shared.AbstractServerLogger;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType.VocabularyTerm;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
@@ -42,7 +43,10 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SampleFetchOption;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SpaceWithProjectsAndRoleAssignments;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.IExperimentId;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.metaproject.IMetaprojectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.IProjectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.ISampleId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Metaproject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
@@ -386,32 +390,31 @@ class GeneralInformationServiceLogger extends AbstractServerLogger implements
         return null;
     }
 
-    // @Override
-    // public List<Attachment> listAttachmentsForProject(String sessionToken,
-    // ProjectIdentifier project, boolean allVersions)
-    // {
-    // logAccess(sessionToken, "listAttachmentsForProject", "PROJECT(%s)", "ALL_VERSIONS(%s)",
-    // project, allVersions);
-    // return null;
-    // }
-    //
-    // @Override
-    // public List<Attachment> listAttachmentsForExperiment(String sessionToken,
-    // ExperimentIdentifier experiment, boolean allVersions)
-    // {
-    // logAccess(sessionToken, "listAttachmentsForExperiment", "EXPERIMENT(%s)",
-    // "ALL_VERSIONS(%s)", experiment, allVersions);
-    // return null;
-    // }
-    //
-    // @Override
-    // public List<Attachment> listAttachmentsForSample(String sessionToken, SampleIdentifier
-    // sample,
-    // boolean allVersions)
-    // {
-    // logAccess(sessionToken, "listAttachmentsForSample", "SAMPLE(%s)", "ALL_VERSIONS(%s)",
-    // sample, allVersions);
-    // return null;
-    // }
-    //
+    @Override
+    public List<Attachment> listAttachmentsForProject(String sessionToken, IProjectId projectId,
+            boolean allVersions)
+    {
+        logAccess(sessionToken, "listAttachmentsForProject", "PROJECT(%s) ALL_VERSIONS(%s)",
+                projectId, allVersions);
+        return null;
+    }
+
+    @Override
+    public List<Attachment> listAttachmentsForExperiment(String sessionToken,
+            IExperimentId experimentId, boolean allVersions)
+    {
+        logAccess(sessionToken, "listAttachmentsForExperiment", "EXPERIMENT(%s) ALL_VERSIONS(%s)",
+                experimentId, allVersions);
+        return null;
+    }
+
+    @Override
+    public List<Attachment> listAttachmentsForSample(String sessionToken, ISampleId sampleId,
+            boolean allVersions)
+    {
+        logAccess(sessionToken, "listAttachmentsForSample", "SAMPLE(%s) ALL_VERSIONS(%s)",
+                sampleId, allVersions);
+        return null;
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ExperimentIdPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ExperimentIdPredicate.java
new file mode 100644
index 00000000000..702f4bf83f0
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ExperimentIdPredicate.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 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.authorization.predicate;
+
+import java.util.List;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.openbis.generic.server.authorization.RoleWithIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.ExperimentIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.ExperimentPermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.ExperimentTechIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.IExperimentId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.PermId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
+
+/**
+ * Predicate for @IExperimentId instances.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class ExperimentIdPredicate extends AbstractExperimentPredicate<IExperimentId>
+{
+
+    @Override
+    public String getCandidateDescription()
+    {
+        return "experiment id";
+    }
+
+    @Override
+    protected Status doEvaluation(PersonPE person, List<RoleWithIdentifier> allowedRoles,
+            IExperimentId experimentId)
+    {
+        assert spacePredicate.initialized : "Predicate has not been initialized";
+        assert experimentTechIdPredicate.initialized : "Predicate has not been initialized";
+        assert experimentPermIdPredicate.initialized : "Predicate has not been initialized";
+        assert experimentAugmentedCodePredicate.initialized : "Predicate has not been initialized";
+
+        if (experimentId instanceof ExperimentIdentifierId)
+        {
+            ExperimentIdentifierId identifierId = (ExperimentIdentifierId) experimentId;
+            return experimentAugmentedCodePredicate.doEvaluation(person, allowedRoles,
+                    identifierId.getIdentifier());
+        }
+        if (experimentId instanceof ExperimentPermIdId)
+        {
+            ExperimentPermIdId permIdId = (ExperimentPermIdId) experimentId;
+            return experimentPermIdPredicate.doEvaluation(person, allowedRoles,
+                    new PermId(permIdId.getPermId()));
+        }
+        if (experimentId instanceof ExperimentTechIdId)
+        {
+            ExperimentTechIdId techIdId = (ExperimentTechIdId) experimentId;
+            return experimentTechIdPredicate.doEvaluation(person, allowedRoles,
+                    new TechId(techIdId.getTechId()));
+        }
+        return Status.createError("Unsupported experiment id: " + experimentId);
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ProjectIdPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ProjectIdPredicate.java
new file mode 100644
index 00000000000..0c958f66c37
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/ProjectIdPredicate.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 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.authorization.predicate;
+
+import java.util.List;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.openbis.generic.server.authorization.RoleWithIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.IProjectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectPermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectTechIdId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
+
+/**
+ * Predicate for @IProjectId instances.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class ProjectIdPredicate extends AbstractProjectPredicate<IProjectId>
+{
+
+    @Override
+    public String getCandidateDescription()
+    {
+        return "project id";
+    }
+
+    @Override
+    protected Status doEvaluation(PersonPE person, List<RoleWithIdentifier> allowedRoles,
+            IProjectId projectId)
+    {
+        assert spacePredicate.initialized : "Predicate has not been initialized";
+        assert projectTechIdPredicate.initialized : "Predicate has not been initialized";
+        assert projectPermIdPredicate.initialized : "Predicate has not been initialized";
+        assert projectAugmentedCodePredicate.initialized : "Predicate has not been initialized";
+
+        if (projectId instanceof ProjectIdentifierId)
+        {
+            ProjectIdentifierId identifierId = (ProjectIdentifierId) projectId;
+            return projectAugmentedCodePredicate.doEvaluation(person, allowedRoles,
+                    identifierId.getIdentifier());
+        }
+        if (projectId instanceof ProjectPermIdId)
+        {
+            ProjectPermIdId permIdId = (ProjectPermIdId) projectId;
+            return projectPermIdPredicate.doEvaluation(person, allowedRoles, permIdId.getPermId());
+        }
+        if (projectId instanceof ProjectTechIdId)
+        {
+            ProjectTechIdId techIdId = (ProjectTechIdId) projectId;
+            return projectTechIdPredicate.doEvaluation(person, allowedRoles,
+                    new TechId(techIdId.getTechId()));
+        }
+        return Status.createError("Unsupported project id: " + projectId);
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/SampleIdPredicate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/SampleIdPredicate.java
new file mode 100644
index 00000000000..2304627e9d0
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/predicate/SampleIdPredicate.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 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.authorization.predicate;
+
+import java.util.List;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.openbis.generic.server.authorization.RoleWithIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.ISampleId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.SampleIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.SamplePermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.SampleTechIdId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.PermId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
+
+/**
+ * Predicate for @ISampleId instances.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class SampleIdPredicate extends AbstractSamplePredicate<ISampleId>
+{
+
+    @Override
+    public String getCandidateDescription()
+    {
+        return "sample id";
+    }
+
+    @Override
+    protected Status doEvaluation(PersonPE person, List<RoleWithIdentifier> allowedRoles,
+            ISampleId sampleId)
+    {
+        assert spacePredicate.initialized : "Predicate has not been initialized";
+        assert sampleTechIdPredicate.initialized : "Predicate has not been initialized";
+        assert samplePermIdPredicate.initialized : "Predicate has not been initialized";
+        assert sampleAugmentedCodePredicate.initialized : "Predicate has not been initialized";
+
+        if (sampleId instanceof SampleIdentifierId)
+        {
+            SampleIdentifierId identifierId = (SampleIdentifierId) sampleId;
+            return sampleAugmentedCodePredicate.doEvaluation(person, allowedRoles,
+                    identifierId.getIdentifier());
+        }
+        if (sampleId instanceof SamplePermIdId)
+        {
+            SamplePermIdId permIdId = (SamplePermIdId) sampleId;
+            return samplePermIdPredicate.doEvaluation(person, allowedRoles,
+                    new PermId(permIdId.getPermId()));
+        }
+        if (sampleId instanceof SampleTechIdId)
+        {
+            SampleTechIdId techIdId = (SampleTechIdId) sampleId;
+            return sampleTechIdPredicate.doEvaluation(person, allowedRoles,
+                    new TechId(techIdId.getTechId()));
+        }
+        return Status.createError("Unsupported sample id: " + sampleId);
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IProjectBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IProjectBO.java
index fe5949d5754..15575af2fec 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IProjectBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IProjectBO.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo;
 import java.util.List;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.IProjectId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
@@ -51,6 +52,12 @@ public interface IProjectBO extends IEntityBusinessObject
      */
     public ProjectPE getProject();
 
+    /**
+     * Returns a project found by the given id or null if it does not exist. Does not change the
+     * state of this object, especially the result of {@link #getProject()}.
+     */
+    public ProjectPE tryFindByProjectId(final IProjectId projectId);
+
     /**
      * Loads a project given by its identifier.
      * 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java
index c8b8f8bb701..e2cf783fdf7 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ProjectBO.java
@@ -30,6 +30,10 @@ import ch.systemsx.cisd.openbis.generic.server.business.IRelationshipService;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDeletionDAO;
 import ch.systemsx.cisd.openbis.generic.server.util.SpaceIdentifierHelper;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.IProjectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectPermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectTechIdId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
@@ -45,6 +49,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
 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.properties.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.managed_property.IManagedPropertyEvaluatorFactory;
 import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
@@ -128,6 +133,39 @@ public final class ProjectBO extends AbstractBusinessObject implements IProjectB
         return project;
     }
 
+    @Override
+    public ProjectPE tryFindByProjectId(IProjectId projectId)
+    {
+        if (projectId == null)
+        {
+            throw new IllegalArgumentException("Project id cannot be null");
+        }
+        if (projectId instanceof ProjectIdentifierId)
+        {
+            ProjectIdentifierId identifierId = (ProjectIdentifierId) projectId;
+            ProjectIdentifier identifier =
+                    new ProjectIdentifierFactory(identifierId.getIdentifier()).createIdentifier();
+            return tryFindByIdentifier(identifier);
+        }
+        if (projectId instanceof ProjectPermIdId)
+        {
+            ProjectPermIdId permIdId = (ProjectPermIdId) projectId;
+            return getProjectDAO().tryGetByPermID(permIdId.getPermId());
+        }
+        if (projectId instanceof ProjectTechIdId)
+        {
+            ProjectTechIdId techIdId = (ProjectTechIdId) projectId;
+            return getProjectDAO().tryGetByTechId(new TechId(techIdId.getTechId()));
+        }
+        throw new IllegalArgumentException("Unsupported project id: " + projectId);
+    }
+
+    private ProjectPE tryFindByIdentifier(ProjectIdentifier identifier)
+    {
+        return getProjectDAO().tryFindProject(identifier.getDatabaseInstanceCode(),
+                identifier.getSpaceCode(), identifier.getProjectCode());
+    }
+
     @Override
     public void define(ProjectIdentifier projectIdentifier, String description,
             List<NewAttachment> attachmentsOrNull, String creatorId) throws UserFailureException
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java
index 8b1b19a0dc7..497867c9e5e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java
@@ -31,6 +31,7 @@ import java.util.Map;
 import org.apache.commons.lang.ObjectUtils;
 
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment.AttachmentInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType.ControlledVocabularyPropertyTypeInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
@@ -39,6 +40,7 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.DataSetInitial
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType.DataSetTypeInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.EntityRegistrationDetails;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.EntityRegistrationDetails.EntityRegistrationDetailsInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment.ExperimentInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Material;
@@ -56,11 +58,17 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SampleFetchOption;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Vocabulary;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Vocabulary.VocabularyInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.VocabularyTerm;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.IObjectId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.AttachmentDownloadConstants;
+import ch.systemsx.cisd.openbis.generic.shared.basic.GenericSharedConstants;
+import ch.systemsx.cisd.openbis.generic.shared.basic.IIdHolder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.PermlinkUtilities;
+import ch.systemsx.cisd.openbis.generic.shared.basic.URLMethodWithParameters;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.CodeWithRegistration;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.CodeWithRegistrationAndModificationDate;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ContainerDataSet;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LinkDataSet;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LinkDataSetUrl;
@@ -68,6 +76,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Metaproject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleLevel;
+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.util.EntityHelper;
 
 /**
@@ -355,7 +365,8 @@ public class Translator
     }
 
     /**
-     * Translates the specified {@link AbstractExternalData} instance into a {@link DataSet} instance.
+     * Translates the specified {@link AbstractExternalData} instance into a {@link DataSet}
+     * instance.
      * 
      * @param connectionsToGet Set of data set connections which should also be translated. This
      *            assumes that the {@link AbstractExternalData} instance is populated with these
@@ -368,7 +379,8 @@ public class Translator
     }
 
     /**
-     * Translates the specified {@link AbstractExternalData} instance into a {@link DataSet} instance.
+     * Translates the specified {@link AbstractExternalData} instance into a {@link DataSet}
+     * instance.
      * 
      * @param connectionsToGet Set of data set connections which should also be translated. This
      *            assumes that the {@link AbstractExternalData} instance is populated with these
@@ -601,55 +613,75 @@ public class Translator
         return list;
     }
 
-    public static List<Attachment> translateAttachments(
-            List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments,
-            boolean allVersions)
+    public static List<Attachment> translateAttachments(String sessionToken,
+            IObjectId attachmentHolderId, AttachmentHolderPE attachmentHolderPE,
+            List<AttachmentPE> attachments, boolean allVersions)
     {
-        Collections.sort(attachments,
-                new Comparator<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment>()
-                    {
-                        @Override
-                        public int compare(
-                                ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment o1,
-                                ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment o2)
-                        {
-                            final int fileNameComp = o1.getFileName().compareTo(o2.getFileName());
-                            // Newest version first.
-                            return (fileNameComp == 0) ? (o2.getVersion() - o1.getVersion())
-                                    : fileNameComp;
-                        }
-                    });
-        final List<Attachment> list = new ArrayList<Attachment>(attachments.size());
+        Collections.sort(attachments, new Comparator<AttachmentPE>()
+            {
+                @Override
+                public int compare(AttachmentPE a1, AttachmentPE a2)
+                {
+                    int fileNameCompare = a1.getFileName().compareTo(a2.getFileName());
+                    // Newest version first.
+                    return fileNameCompare == 0 ? a2.getVersion() - a1.getVersion()
+                            : fileNameCompare;
+                }
+            });
+        List<Attachment> result = new ArrayList<Attachment>();
         String lastFilenameSeen = null;
-        for (ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment attachment : attachments)
+        for (AttachmentPE attachmentPE : attachments)
         {
             // The newest version will be first. If allVersions == false, skip all the older
             // versions.
-            if (allVersions == false
-                    && ObjectUtils.equals(lastFilenameSeen, attachment.getFileName()))
+            String fileName = attachmentPE.getFileName();
+            if (allVersions == false && ObjectUtils.equals(lastFilenameSeen, fileName))
             {
                 continue;
             }
-            list.add(translate(attachment));
-            lastFilenameSeen = attachment.getFileName();
+            result.add(translate(sessionToken, attachmentHolderId, attachmentHolderPE, attachmentPE));
+            lastFilenameSeen = fileName;
         }
-        return list;
+        return result;
     }
 
-    public static Attachment translate(
-            ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment attachment)
+    private static Attachment translate(String sessionToken, IObjectId attachmentHolderId,
+            AttachmentHolderPE attachmentHolderPE, AttachmentPE attachment)
     {
-        final Attachment.AttachmentInitializer initializer = new Attachment.AttachmentInitializer();
+        AttachmentInitializer initializer = new AttachmentInitializer();
+        initializer.setAttachmentHolderId(attachmentHolderId);
         initializer.setFileName(attachment.getFileName());
         initializer.setVersion(attachment.getVersion());
         initializer.setTitle(attachment.getTitle());
         initializer.setDescription(attachment.getDescription());
-        initializer.setRegistrationDate(attachment.getRegistrationDate());
-        initializer.setUserId(attachment.getRegistrator().getUserId());
-        initializer.setUserEmail(attachment.getRegistrator().getEmail());
-        initializer.setUserFirstName(attachment.getRegistrator().getFirstName());
-        initializer.setUserLastName(attachment.getRegistrator().getLastName());
-        initializer.setPermLink(attachment.getPermlink());
+        EntityRegistrationDetailsInitializer regDetailsInitializer =
+                new EntityRegistrationDetailsInitializer();
+        regDetailsInitializer.setRegistrationDate(attachment.getRegistrationDate());
+        regDetailsInitializer.setUserId(attachment.getRegistrator().getUserId());
+        regDetailsInitializer.setFirstName(attachment.getRegistrator().getFirstName());
+        regDetailsInitializer.setLastName(attachment.getRegistrator().getLastName());
+        regDetailsInitializer.setEmail(attachment.getRegistrator().getEmail());
+        initializer.setRegistrationDetails(new EntityRegistrationDetails(regDetailsInitializer));
+        URLMethodWithParameters url =
+                new URLMethodWithParameters("/openbis/"
+                        + AttachmentDownloadConstants.ATTACHMENT_DOWNLOAD_SERVLET_NAME);
+        url.addParameter(GenericSharedConstants.SESSION_ID_PARAMETER, sessionToken);
+        url.addParameter(AttachmentDownloadConstants.ATTACHMENT_HOLDER_PARAMETER,
+                attachmentHolderPE.getAttachmentHolderKind().toString());
+        if (attachmentHolderPE instanceof IIdHolder)
+        {
+            IIdHolder idHolder = (IIdHolder) attachmentHolderPE;
+            url.addParameter(AttachmentDownloadConstants.TECH_ID_PARAMETER, idHolder.getId()
+                    .toString());
+        } else
+        {
+            url.addParameter(PermlinkUtilities.PERM_ID_PARAMETER_KEY,
+                    attachmentHolderPE.getPermId());
+        }
+        url.addParameter(AttachmentDownloadConstants.FILE_NAME_PARAMETER, attachment.getFileName());
+        url.addParameter(AttachmentDownloadConstants.VERSION_PARAMETER,
+                Integer.toString(attachment.getVersion()));
+        initializer.setDownloadLink(url.toString());
         return new Attachment(initializer);
     }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/AttachmentDownloadConstants.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/AttachmentDownloadConstants.java
new file mode 100644
index 00000000000..220297e6f93
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/AttachmentDownloadConstants.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 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.basic;
+
+/**
+ * Some constants needed to create attachment download URLs.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class AttachmentDownloadConstants
+{
+    /** Name of the servlet to download an experiment attachment. */
+    public static final String ATTACHMENT_DOWNLOAD_SERVLET_NAME = "attachment-download";
+
+    /** The HTTP URL parameter used to specify the technical id. */
+    public static final String TECH_ID_PARAMETER = "id";
+
+    /** The HTTP URL parameter used to specify the version. */
+    public static final String VERSION_PARAMETER = "version";
+
+    /** The HTTP URL parameter used to specify the file name. */
+    public static final String FILE_NAME_PARAMETER = "fileName";
+
+    /** The HTTP URL parameter used to specify the attachment holder. */
+    public static final String ATTACHMENT_HOLDER_PARAMETER = "attachmentHolder";
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServlet.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServlet.java
index 647e7330a92..6f6a4ab3df3 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServlet.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServlet.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.openbis.plugin.generic.client.web.server;
 
+import static ch.systemsx.cisd.openbis.generic.shared.basic.AttachmentDownloadConstants.ATTACHMENT_DOWNLOAD_SERVLET_NAME;
+
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 
@@ -25,11 +27,15 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.servlet.mvc.AbstractCommandController;
 
 import ch.rinn.restrictions.Private;
-import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericConstants;
+import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientService;
 import ch.systemsx.cisd.openbis.generic.client.web.server.AbstractFileDownloadServlet;
+import ch.systemsx.cisd.openbis.generic.shared.ResourceNames;
+import ch.systemsx.cisd.openbis.generic.shared.basic.AttachmentDownloadConstants;
+import ch.systemsx.cisd.openbis.generic.shared.basic.PermlinkUtilities;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AttachmentHolderKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AttachmentWithContent;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
 
 /**
@@ -39,16 +45,19 @@ import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
  */
 @Controller
 @RequestMapping(
-    { "/attachment-download", "/openbis/attachment-download" })
+    { "/" + ATTACHMENT_DOWNLOAD_SERVLET_NAME, "/openbis/" + ATTACHMENT_DOWNLOAD_SERVLET_NAME })
 public class AttachmentDownloadServlet extends AbstractFileDownloadServlet
 {
 
     @Resource(name = ch.systemsx.cisd.openbis.plugin.generic.shared.ResourceNames.GENERIC_PLUGIN_SERVER)
     private IGenericServer server;
 
+    @Resource(name = ResourceNames.COMMON_SERVICE)
+    private ICommonClientService commonService;
+
     public AttachmentDownloadServlet()
     {
-
+        setRequireSession(false);
     }
 
     // For testing purposes only
@@ -61,7 +70,8 @@ public class AttachmentDownloadServlet extends AbstractFileDownloadServlet
     @Override
     protected FileContent getFileContent(final HttpServletRequest request) throws Exception
     {
-        final String versionStringOrNull = request.getParameter(GenericConstants.VERSION_PARAMETER);
+        final String versionStringOrNull =
+                request.getParameter(AttachmentDownloadConstants.VERSION_PARAMETER);
         Integer versionOrNull = null;
 
         if (versionStringOrNull != null)
@@ -69,20 +79,31 @@ public class AttachmentDownloadServlet extends AbstractFileDownloadServlet
             versionOrNull = Integer.parseInt(versionStringOrNull);
         }
 
-        String fileName = request.getParameter(GenericConstants.FILE_NAME_PARAMETER);
+        String fileName = request.getParameter(AttachmentDownloadConstants.FILE_NAME_PARAMETER);
         String encoding = request.getCharacterEncoding();
         if (encoding == null)
         {
             encoding = "ISO-8859-1";
         }
-        final String techIdString = request.getParameter(GenericConstants.TECH_ID_PARAMETER);
+        final String techIdString =
+                request.getParameter(AttachmentDownloadConstants.TECH_ID_PARAMETER);
         final String attachmentHolderKind =
-                request.getParameter(GenericConstants.ATTACHMENT_HOLDER_PARAMETER);
-
-        if (StringUtils.isNotBlank(fileName) && StringUtils.isNotBlank(techIdString)
+                request.getParameter(AttachmentDownloadConstants.ATTACHMENT_HOLDER_PARAMETER);
+        TechId techId = null;
+        if (StringUtils.isNotBlank(techIdString))
+        {
+            techId = new TechId(Long.parseLong(techIdString));
+        } else
+        {
+            String permId = request.getParameter(PermlinkUtilities.PERM_ID_PARAMETER_KEY);
+            if (StringUtils.isNotBlank(permId))
+            {
+                techId = new TechId(getTechId(attachmentHolderKind, permId));
+            }
+        }
+        if (StringUtils.isNotBlank(fileName) && techId != null
                 && StringUtils.isNotBlank(attachmentHolderKind))
         {
-            final TechId techId = new TechId(Long.parseLong(techIdString));
             if (attachmentHolderKind.equals(AttachmentHolderKind.EXPERIMENT.name()))
             {
                 return getExperimentFile(request, versionOrNull, fileName, techId);
@@ -97,6 +118,21 @@ public class AttachmentDownloadServlet extends AbstractFileDownloadServlet
         return null;
     }
 
+    private Long getTechId(String attachmentHolderKind, String permId)
+    {
+        if (attachmentHolderKind.equals(AttachmentHolderKind.EXPERIMENT.name()))
+        {
+            return commonService.getEntityInformationHolder(EntityKind.EXPERIMENT, permId).getId();
+        } else if (attachmentHolderKind.equals(AttachmentHolderKind.SAMPLE.name()))
+        {
+            return commonService.getProjectInfoByPermId(permId).getId();
+        } else if (attachmentHolderKind.equals(AttachmentHolderKind.PROJECT.name()))
+        {
+            return commonService.getEntityInformationHolder(EntityKind.SAMPLE, permId).getId();
+        }
+        return null;
+    }
+
     private FileContent getExperimentFile(final HttpServletRequest request,
             final Integer versionOrNull, final String fileName, final TechId experimentId)
     {
@@ -123,4 +159,5 @@ public class AttachmentDownloadServlet extends AbstractFileDownloadServlet
                         versionOrNull);
         return new FileContent(attachment.getContent(), attachment.getFileName());
     }
+
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServletTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServletTest.java
index 795eddfd12e..7ed0c7c2c3f 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServletTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/AttachmentDownloadServletTest.java
@@ -27,10 +27,11 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import ch.rinn.restrictions.Friend;
-import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericConstants;
 import ch.systemsx.cisd.openbis.generic.client.web.server.AbstractFileDownloadServlet.FileContent;
 import ch.systemsx.cisd.openbis.generic.server.SessionConstants;
 import ch.systemsx.cisd.openbis.generic.shared.CommonTestUtils;
+import ch.systemsx.cisd.openbis.generic.shared.basic.AttachmentDownloadConstants;
+import ch.systemsx.cisd.openbis.generic.shared.basic.GenericSharedConstants;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AttachmentHolderKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AttachmentWithContent;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
@@ -87,16 +88,20 @@ public final class AttachmentDownloadServletTest
         context.checking(new Expectations()
             {
                 {
-                    one(servletRequest).getParameter(GenericConstants.VERSION_PARAMETER);
+                    one(servletRequest).getParameter(AttachmentDownloadConstants.VERSION_PARAMETER);
                     will(returnValue(CommonTestUtils.VERSION_22 + ""));
-                    one(servletRequest).getParameter(GenericConstants.FILE_NAME_PARAMETER);
+                    one(servletRequest).getParameter(
+                            AttachmentDownloadConstants.FILE_NAME_PARAMETER);
                     will(returnValue(CommonTestUtils.FILENAME));
                     one(servletRequest).getCharacterEncoding();
                     will(returnValue(null));
-                    one(servletRequest).getParameter(GenericConstants.ATTACHMENT_HOLDER_PARAMETER);
+                    one(servletRequest).getParameter(
+                            AttachmentDownloadConstants.ATTACHMENT_HOLDER_PARAMETER);
                     will(returnValue(AttachmentHolderKind.EXPERIMENT.name()));
-                    one(servletRequest).getParameter(GenericConstants.TECH_ID_PARAMETER);
+                    one(servletRequest).getParameter(AttachmentDownloadConstants.TECH_ID_PARAMETER);
                     will(returnValue(CommonTestUtils.TECH_ID.toString()));
+                    one(servletRequest).getParameter(GenericSharedConstants.SESSION_ID_PARAMETER);
+                    will(returnValue(null));
 
                     one(servletRequest).getSession(false);
                     will(Expectations.returnValue(httpSession));
@@ -114,8 +119,8 @@ public final class AttachmentDownloadServletTest
             });
         FileContent fileContent = createServlet().getFileContent(servletRequest);
         AssertJUnit.assertEquals(attachmentPE.getFileName(), fileContent.getFileName());
-        AssertJUnit.assertEquals(attachmentPE.getAttachmentContent().getValue(), fileContent
-                .getContent());
+        AssertJUnit.assertEquals(attachmentPE.getAttachmentContent().getValue(),
+                fileContent.getContent());
         context.assertIsSatisfied();
     }
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java
index 93a7e3d0ed7..4a1eaf90a46 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java
@@ -39,7 +39,9 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException;
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
@@ -64,7 +66,15 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchCl
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClauseTimeAttribute;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchSubCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SpaceWithProjectsAndRoleAssignments;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.ExperimentIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.ExperimentPermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.ExperimentTechIdId;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.metaproject.MetaprojectIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.ProjectPermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.SampleIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.SamplePermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.SampleTechIdId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Metaproject;
@@ -1662,197 +1672,194 @@ public class GeneralInformationServiceTest extends SystemTestCase
         assertEquals("TEST_METAPROJECTS", metaprojectAssignments.getMetaproject().getName());
     }
 
-    // @Test
-    // public void testListAttachmentsForExperimentAllVersions() throws ParseException
-    // {
-    // final List<Experiment> experiments =
-    // generalInformationService.listExperiments(sessionToken,
-    // Collections.singletonList("/CISD/NEMO/EXP1"));
-    // assertEquals(1, experiments.size());
-    //
-    // final List<Attachment> attachments =
-    // generalInformationService.listAttachmentsForExperiment(sessionToken,
-    // ExperimentIdentifier.createFromEntity(experiments.get(0)),
-    // true);
-    // assertEquals(4, attachments.size());
-    // int version = 4;
-    // final String[] regDates =
-    // new String[]
-    // { "2008-12-10 13:49:27.901 +0100", "2008-12-10 13:49:20.236 +0100",
-    // "2008-12-10 13:49:14.564 +0100", "2008-12-10 13:48:17.996 +0100" };
-    // for (Attachment a : attachments)
-    // {
-    // assertEquals("exampleExperiments.txt", a.getFileName());
-    // assertEquals(version, a.getVersion());
-    // assertEquals("", a.getTitle());
-    // assertEquals("", a.getDescription());
-    // final Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z")
-    // .parse(regDates[4 - version]);
-    // assertEquals(
-    // date,
-    // a.getRegistrationDate());
-    // assertEquals("test", a.getUserId());
-    // assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
-    // assertNotNull(a.getUserFirstName());
-    // assertNotNull(a.getUserLastName());
-    // assertEquals(
-    // String.format(
-    // "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=exampleExperiments.txt&version=%d&entity=EXPERIMENT&permId=200811050951882-1028",
-    // version),
-    // a.getPermLink());
-    // --version;
-    // }
-    // }
-    //
-    // @Test
-    // public void testListAttachmentsForExperimentLatestVersion() throws ParseException
-    // {
-    // final List<Experiment> experiments =
-    // generalInformationService.listExperiments(sessionToken,
-    // Collections.singletonList("/CISD/NEMO/EXP1"));
-    // assertEquals(1, experiments.size());
-    //
-    // final List<Attachment> attachments =
-    // generalInformationService.listAttachmentsForExperiment(sessionToken,
-    // ExperimentIdentifier.createFromEntity(experiments.get(0)), false);
-    // assertEquals(1, attachments.size());
-    // final Attachment a = attachments.get(0);
-    // assertEquals("exampleExperiments.txt", a.getFileName());
-    // assertEquals(4, a.getVersion());
-    // assertEquals("", a.getTitle());
-    // assertEquals("", a.getDescription());
-    // final Date date =
-    // new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z")
-    // .parse("2008-12-10 13:49:27.901 +0100");
-    // assertEquals(date, a.getRegistrationDate());
-    // assertEquals("test", a.getUserId());
-    // assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
-    // assertNotNull(a.getUserFirstName());
-    // assertNotNull(a.getUserLastName());
-    // assertEquals(
-    // "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=exampleExperiments.txt&version=4&entity=EXPERIMENT&permId=200811050951882-1028",
-    // a.getPermLink());
-    //
-    // final List<Attachment> attachments2 =
-    // generalInformationService.listAttachmentsForExperiment(sessionToken,
-    // ExperimentIdentifier.createFromAugmentedCode("/CISD/NEMO/EXP1"), false);
-    //
-    // assertEquals(1, attachments2.size());
-    //
-    // final Attachment a2 = attachments2.get(0);
-    // assertEquals(a, a2);
-    // }
-    //
-    // @Test
-    // public void testListAttachmentsForSample() throws ParseException
-    // {
-    // SearchCriteria searchCriteria = new SearchCriteria();
-    // searchCriteria.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE,
-    // "3VCP6"));
-    //
-    // final List<Sample> samples =
-    // generalInformationService.searchForSamples(sessionToken, searchCriteria);
-    // assertEquals(1, samples.size());
-    //
-    // final List<Attachment> attachments =
-    // generalInformationService.listAttachmentsForSample(sessionToken,
-    // SampleIdentifier.createFromEntity(samples.get(0)),
-    // true);
-    // assertEquals(1, attachments.size());
-    //
-    // final Attachment a = attachments.get(0);
-    // assertEquals("sampleHistory.txt", a.getFileName());
-    // assertEquals("", a.getTitle());
-    // assertEquals("", a.getDescription());
-    // assertEquals(1, a.getVersion());
-    // final Date date =
-    // new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z")
-    // .parse("2009-06-09 17:00:00.000 +0200");
-    // assertEquals(date, a.getRegistrationDate());
-    // assertEquals("test", a.getUserId());
-    // assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
-    // assertNotNull(a.getUserFirstName());
-    // assertNotNull(a.getUserLastName());
-    // assertEquals(
-    // "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=sampleHistory.txt&version=1&entity=SAMPLE&permId=200811050946559-980",
-    // a.getPermLink());
-    //
-    // final List<Attachment> attachments2 =
-    // generalInformationService.listAttachmentsForSample(sessionToken,
-    // SampleIdentifier.createFromPermId("200811050946559-980"), true);
-    //
-    // assertEquals(1, attachments2.size());
-    //
-    // final Attachment a2 = attachments2.get(0);
-    // assertEquals(a, a2);
-    //
-    // final List<Attachment> attachments3 =
-    // generalInformationService.listAttachmentsForSample(sessionToken,
-    // SampleIdentifier.createFromAugmentedCode("/CISD/3VCP6"), true);
-    //
-    // assertEquals(1, attachments3.size());
-    //
-    // final Attachment a3 = attachments3.get(0);
-    // assertEquals(a, a3);
-    // }
-    //
-    // @Test
-    // public void testListAttachmentsForProjectNoAttachment()
-    // {
-    // final List<Attachment> attachments =
-    // generalInformationService.listAttachmentsForProject(sessionToken,
-    // ProjectIdentifier.createFromAugmentedCode("/CISD/DEFAULT"), true);
-    //
-    // assertEquals(0, attachments.size());
-    // }
-    //
-    // @Test
-    // public void testListAttachmentsForProjectNonExisting()
-    // {
-    // final List<Attachment> attachments =
-    // generalInformationService.listAttachmentsForProject(sessionToken,
-    // ProjectIdentifier.createFromAugmentedCode("/NONE/EXISTENT"), true);
-    //
-    // assertEquals(0, attachments.size());
-    // }
-    //
-    // @Test
-    // public void testListAttachmentsForProject() throws ParseException
-    // {
-    // final List<Attachment> attachments =
-    // generalInformationService.listAttachmentsForProject(sessionToken,
-    // ProjectIdentifier.createFromPermId("20120814110011738-103"), true);
-    //
-    // assertEquals(1, attachments.size());
-    //
-    // final Attachment a = attachments.get(0);
-    // assertEquals("projectDescription.txt", a.getFileName());
-    // assertEquals("The Project", a.getTitle());
-    // assertEquals("All about it.", a.getDescription());
-    // assertEquals(1, a.getVersion());
-    // final Date date =
-    // new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z")
-    // .parse("2012-01-03 08:27:57.123 +0100");
-    // assertEquals(date, a.getRegistrationDate());
-    // assertEquals("test", a.getUserId());
-    // assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
-    // assertNotNull(a.getUserFirstName());
-    // assertNotNull(a.getUserLastName());
-    // assertEquals(
-    // "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=projectDescription.txt&version=1&entity=PROJECT&code=NEMO&space=CISD",
-    // a.getPermLink());
-    //
-    // final List<Attachment> attachments2 =
-    // generalInformationService.listAttachmentsForProject(sessionToken,
-    // ProjectIdentifier.createFromAugmentedCode("/CISD/NEMO"), true);
-    //
-    // assertEquals(1, attachments2.size());
-    //
-    // final Attachment a2 = attachments2.get(0);
-    // assertEquals(a, a2);
-    //
-    // }
-    //
+    @Test
+    public void testListAttachmentsForExperimentAllVersions() throws ParseException
+    {
+        final List<Experiment> experiments =
+                generalInformationService.listExperiments(sessionToken,
+                        Collections.singletonList("/CISD/NEMO/EXP1"));
+        assertEquals(1, experiments.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForExperiment(sessionToken,
+                        new ExperimentTechIdId(experiments.get(0).getId()), true);
+        assertEquals(4, attachments.size());
+        int version = 4;
+        final String[] regDates =
+                new String[]
+                    { "2008-12-10 13:49:27.901 +0100", "2008-12-10 13:49:20.236 +0100",
+                            "2008-12-10 13:49:14.564 +0100", "2008-12-10 13:48:17.996 +0100" };
+        for (Attachment a : attachments)
+        {
+            assertEquals("exampleExperiments.txt", a.getFileName());
+            assertEquals(version, a.getVersion());
+            assertEquals("", a.getTitle());
+            assertEquals("", a.getDescription());
+            final Date date =
+                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z").parse(regDates[4 - version]);
+            assertEquals(date, a.getRegistrationDetails().getRegistrationDate());
+            assertEquals("test", a.getRegistrationDetails().getUserId());
+            assertEquals("franz-josef.elmer@systemsx.ch", a.getRegistrationDetails().getUserEmail());
+            assertNotNull(a.getRegistrationDetails().getUserFirstName());
+            assertNotNull(a.getRegistrationDetails().getUserLastName());
+            assertEquals("/openbis/attachment-download?sessionID=" + sessionToken
+                    + "&attachmentHolder=EXPERIMENT&id=2&fileName=exampleExperiments.txt&version="
+                    + version, a.getDownloadLink());
+            --version;
+        }
+    }
+
+    @Test
+    public void testListAttachmentsForExperimentLatestVersion() throws ParseException
+    {
+        final List<Experiment> experiments =
+                generalInformationService.listExperiments(sessionToken,
+                        Collections.singletonList("/CISD/NEMO/EXP1"));
+        assertEquals(1, experiments.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForExperiment(sessionToken,
+                        new ExperimentPermIdId(experiments.get(0).getPermId()), false);
+        assertEquals(1, attachments.size());
+        final Attachment a = attachments.get(0);
+        assertEquals("exampleExperiments.txt", a.getFileName());
+        assertEquals(4, a.getVersion());
+        assertEquals("", a.getTitle());
+        assertEquals("", a.getDescription());
+        final Date date =
+                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z")
+                        .parse("2008-12-10 13:49:27.901 +0100");
+        assertEquals(date, a.getRegistrationDetails().getRegistrationDate());
+        assertEquals("test", a.getRegistrationDetails().getUserId());
+        assertEquals("franz-josef.elmer@systemsx.ch", a.getRegistrationDetails().getUserEmail());
+        assertNotNull(a.getRegistrationDetails().getUserFirstName());
+        assertNotNull(a.getRegistrationDetails().getUserLastName());
+        assertEquals("/openbis/attachment-download?sessionID=" + sessionToken
+                + "&attachmentHolder=EXPERIMENT&id=2&fileName=exampleExperiments.txt&version=4",
+                a.getDownloadLink());
+
+        final List<Attachment> attachments2 =
+                generalInformationService.listAttachmentsForExperiment(sessionToken,
+                        new ExperimentIdentifierId("/CISD/NEMO/EXP1"), false);
+
+        assertEquals(1, attachments2.size());
+
+        final Attachment a2 = attachments2.get(0);
+        assertEquals(a, a2);
+    }
+
+    @Test
+    public void testListAttachmentsForSample() throws ParseException
+    {
+        SearchCriteria searchCriteria = new SearchCriteria();
+        searchCriteria.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE,
+                "3VCP6"));
+
+        final List<Sample> samples =
+                generalInformationService.searchForSamples(sessionToken, searchCriteria);
+        assertEquals(1, samples.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForSample(sessionToken,
+                        new SampleTechIdId(samples.get(0).getId()), true);
+        assertEquals(1, attachments.size());
+
+        final Attachment a = attachments.get(0);
+        assertEquals("sampleHistory.txt", a.getFileName());
+        assertEquals("", a.getTitle());
+        assertEquals("", a.getDescription());
+        assertEquals(1, a.getVersion());
+        final Date date =
+                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z")
+                        .parse("2009-06-09 17:00:00.000 +0200");
+        assertEquals(date, a.getRegistrationDetails().getRegistrationDate());
+        assertEquals("test", a.getRegistrationDetails().getUserId());
+        assertEquals("franz-josef.elmer@systemsx.ch", a.getRegistrationDetails().getUserEmail());
+        assertNotNull(a.getRegistrationDetails().getUserFirstName());
+        assertNotNull(a.getRegistrationDetails().getUserLastName());
+        assertEquals("/openbis/attachment-download?sessionID=" + sessionToken
+                + "&attachmentHolder=SAMPLE&id=987&fileName=sampleHistory.txt&version=1",
+                a.getDownloadLink());
+
+        final List<Attachment> attachments2 =
+                generalInformationService.listAttachmentsForSample(sessionToken,
+                        new SamplePermIdId("200811050946559-980"), true);
+
+        assertEquals(1, attachments2.size());
+
+        final Attachment a2 = attachments2.get(0);
+        assertEquals(a, a2);
+
+        final List<Attachment> attachments3 =
+                generalInformationService.listAttachmentsForSample(sessionToken,
+                        new SampleIdentifierId("/CISD/3VCP6"), true);
+
+        assertEquals(1, attachments3.size());
+
+        final Attachment a3 = attachments3.get(0);
+        assertEquals(a, a3);
+    }
+
+    @Test
+    public void testListAttachmentsForProjectNoAttachment()
+    {
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForProject(sessionToken,
+                        new ProjectIdentifierId("/CISD/DEFAULT"), true);
+
+        assertEquals(0, attachments.size());
+    }
+
+    @Test
+    public void testListAttachmentsForProjectNonExisting()
+    {
+        try
+        {
+            generalInformationService.listAttachmentsForProject(sessionToken,
+                    new ProjectIdentifierId("/NONE/EXISTENT"), true);
+        } catch (UserFailureException ex)
+        {
+            assertEquals("No project found for id '/NONE/EXISTENT'.", ex.getMessage());
+        }
+    }
+
+    @Test
+    public void testListAttachmentsForProject() throws ParseException
+    {
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForProject(sessionToken,
+                        new ProjectPermIdId("20120814110011738-103"), true);
+
+        assertEquals(1, attachments.size());
+
+        final Attachment a = attachments.get(0);
+        assertEquals("projectDescription.txt", a.getFileName());
+        assertEquals("The Project", a.getTitle());
+        assertEquals("All about it.", a.getDescription());
+        assertEquals(1, a.getVersion());
+        final Date date =
+                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z")
+                        .parse("2012-01-03 08:27:57.123 +0100");
+        assertEquals(date, a.getRegistrationDetails().getRegistrationDate());
+        assertEquals("test", a.getRegistrationDetails().getUserId());
+        assertEquals("franz-josef.elmer@systemsx.ch", a.getRegistrationDetails().getUserEmail());
+        assertNotNull(a.getRegistrationDetails().getUserFirstName());
+        assertNotNull(a.getRegistrationDetails().getUserLastName());
+        assertEquals("/openbis/attachment-download?sessionID=" + sessionToken
+                + "&attachmentHolder=PROJECT&id=3&fileName=projectDescription.txt&version=1",
+                a.getDownloadLink());
+
+        final List<Attachment> attachments2 =
+                generalInformationService.listAttachmentsForProject(sessionToken,
+                        new ProjectIdentifierId("/CISD/NEMO"), true);
+
+        assertEquals(1, attachments2.size());
+
+        final Attachment a2 = attachments2.get(0);
+        assertEquals(a, a2);
+
+    }
+
     private void sortDataSets(List<DataSet> dataSets)
     {
         Collections.sort(dataSets, new Comparator<DataSet>()
diff --git a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java
index 646b68534ad..67c06293cf6 100644
--- a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java
+++ b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java
@@ -24,6 +24,7 @@ import java.util.Set;
 
 import ch.systemsx.cisd.common.api.IRpcService;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
@@ -40,7 +41,10 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SampleFetchOption;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SpaceWithProjectsAndRoleAssignments;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.IExperimentId;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.metaproject.IMetaprojectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.IProjectId;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.ISampleId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Metaproject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
 
@@ -486,4 +490,33 @@ public interface IGeneralInformationService extends IRpcService
      */
     public MetaprojectAssignments getMetaproject(String sessionToken, IMetaprojectId metaprojectId);
 
+    /**
+     * Lists attachments of specified project.
+     * 
+     * @param allVersions If <code>true</code>, return all versions of the attachments, otherwise
+     *            return only the latest version.
+     * @since 1.22
+     */
+    public List<Attachment> listAttachmentsForProject(String sessionToken,
+            IProjectId projectId, boolean allVersions);
+    
+    /**
+     * Lists attachments of specified experiment.
+     * 
+     * @param allVersions If <code>true</code>, return all versions of the attachments, otherwise
+     *            return only the latest version.
+     * @since 1.22
+     */
+    public List<Attachment> listAttachmentsForExperiment(String sessionToken,
+            IExperimentId experimentId, boolean allVersions);
+    
+    /**
+     * Lists attachments of specified sample.
+     * 
+     * @param allVersions If <code>true</code>, return all versions of the attachments, otherwise
+     *            return only the latest version.
+     * @since 1.22
+     */
+    public List<Attachment> listAttachmentsForSample(String sessionToken,
+            ISampleId sampleId, boolean allVersions);
 }
diff --git a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Attachment.java b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Attachment.java
index 31bc161c62e..d19b95a3d5c 100644
--- a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Attachment.java
+++ b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Attachment.java
@@ -21,6 +21,7 @@ import java.util.Date;
 
 import ch.systemsx.cisd.base.annotation.JsonObject;
 import ch.systemsx.cisd.common.shared.basic.string.StringUtils;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.IObjectId;
 
 /**
  * Immutable value object representing an attachment.
@@ -34,6 +35,8 @@ public class Attachment implements Serializable, Comparable<Attachment>
 {
     private static final long serialVersionUID = 1L;
 
+    private IObjectId attachmentHolderId;
+    
     private String fileName;
 
     private int version;
@@ -42,20 +45,14 @@ public class Attachment implements Serializable, Comparable<Attachment>
 
     private String description;
 
-    private Date registrationDate;
-
-    private String userFirstName;
-
-    private String userLastName;
-
-    private String userEmail;
-
-    private String userId;
-
-    private String permLink;
+    private EntityRegistrationDetails registrationDetails;
+    
+    private String downloadLink;
 
     public static class AttachmentInitializer
     {
+        private IObjectId attachmentHolderId;
+        
         private String fileName;
 
         private int version;
@@ -64,17 +61,19 @@ public class Attachment implements Serializable, Comparable<Attachment>
 
         private String description;
 
-        private Date registrationDate;
+        private EntityRegistrationDetails registrationDetails;
+        
+        private String downloadLink;
 
-        private String userFirstName;
-
-        private String userLastName;
-
-        private String userEmail;
-
-        private String userId;
+        public IObjectId getAttachmentHolderId()
+        {
+            return attachmentHolderId;
+        }
 
-        private String permLink;
+        public void setAttachmentHolderId(IObjectId attachmentHolderId)
+        {
+            this.attachmentHolderId = attachmentHolderId;
+        }
 
         public String getFileName()
         {
@@ -116,64 +115,24 @@ public class Attachment implements Serializable, Comparable<Attachment>
             this.description = description;
         }
 
-        public Date getRegistrationDate()
-        {
-            return registrationDate;
-        }
-
-        public void setRegistrationDate(Date registrationDate)
-        {
-            this.registrationDate = registrationDate;
-        }
-
-        public String getUserFirstName()
-        {
-            return userFirstName;
-        }
-
-        public void setUserFirstName(String userFirstName)
-        {
-            this.userFirstName = userFirstName;
-        }
-
-        public String getUserLastName()
-        {
-            return userLastName;
-        }
-
-        public void setUserLastName(String userLastName)
-        {
-            this.userLastName = userLastName;
-        }
-
-        public String getUserEmail()
-        {
-            return userEmail;
-        }
-
-        public void setUserEmail(String userEmail)
-        {
-            this.userEmail = userEmail;
-        }
-
-        public String getUserId()
+        public EntityRegistrationDetails getRegistrationDetails()
         {
-            return userId;
+            return registrationDetails;
         }
 
-        public void setUserId(String userId)
+        public void setRegistrationDetails(EntityRegistrationDetails registrationDetails)
         {
-            this.userId = userId;
+            this.registrationDetails = registrationDetails;
         }
 
-        public String getPermLink()
+        public String getDownloadLink()
         {
-            return permLink;
+            return downloadLink;
         }
 
-        public void setPermLink(String permLink)
+        public void setDownloadLink(String downloadLink)
         {
-            this.permLink = permLink;
+            this.downloadLink = downloadLink;
         }
 
     }
@@ -184,16 +143,22 @@ public class Attachment implements Serializable, Comparable<Attachment>
 
     public Attachment(AttachmentInitializer initializer)
     {
+        setAttachmentHolderId(initializer.getAttachmentHolderId());
         setFileName(initializer.getFileName());
         setVersion(initializer.getVersion());
-        setRegistrationDate(initializer.getRegistrationDate());
         setTitle(initializer.getTitle());
         setDescription(initializer.getDescription());
-        setUserId(initializer.getUserId());
-        setUserEmail(initializer.getUserEmail());
-        setUserFirstName(initializer.getUserFirstName());
-        setUserLastName(initializer.getUserLastName());
-        setPermLink(initializer.getPermLink());
+        setRegistrationDetails(initializer.getRegistrationDetails());
+        setDownloadLink(initializer.getDownloadLink());
+    }
+
+    /**
+     * Returns the id of the entity holding this attachment. This will be the id which has been
+     * used to retrieve this attachment.
+     */
+    public IObjectId getAttachmentHolderId()
+    {
+        return attachmentHolderId;
     }
 
     /**
@@ -230,53 +195,21 @@ public class Attachment implements Serializable, Comparable<Attachment>
     }
 
     /**
-     * Returns the date when this attachment was uploaded.
-     */
-    public Date getRegistrationDate()
-    {
-        return registrationDate;
-    }
-
-    /**
-     * Returns the first name of the user who uploaded this attachment.
+     * Return the registration details.
      */
-    public String getUserFirstName()
+    public EntityRegistrationDetails getRegistrationDetails()
     {
-        return userFirstName;
+        return registrationDetails;
     }
 
     /**
-     * Returns the last name of the user who uploaded this attachment.
+     * Returns the download link. In combination with the basic URL (containing host name and port)
+     * it can be used to create a URL for downloading the attachment file.
      */
-    public String getUserLastName()
+    public String getDownloadLink()
     {
-        return userLastName;
+        return downloadLink;
     }
-
-    /**
-     * Returns the email of the user who uploaded this attachment.
-     */
-    public String getUserEmail()
-    {
-        return userEmail;
-    }
-
-    /**
-     * Returns the user id of the user who uploaded this attachment.
-     */
-    public String getUserId()
-    {
-        return userId;
-    }
-
-    /**
-     * Returns the permanent hyperlink of this attachment.
-     */
-    public String getPermLink()
-    {
-        return permLink;
-    }
-
     //
     // JSON-RPC
     //
@@ -305,34 +238,19 @@ public class Attachment implements Serializable, Comparable<Attachment>
         this.description = StringUtils.isBlank(description) ? "" : description;
     }
 
-    private void setRegistrationDate(Date registrationDate)
-    {
-        this.registrationDate = registrationDate;
-    }
-
-    private void setUserFirstName(String userFirstName)
-    {
-        this.userFirstName = userFirstName;
-    }
-
-    private void setUserLastName(String userLastName)
-    {
-        this.userLastName = userLastName;
-    }
-
-    private void setUserEmail(String userEmail)
+    private void setAttachmentHolderId(IObjectId attachmentHolderId)
     {
-        this.userEmail = userEmail;
+        this.attachmentHolderId = attachmentHolderId;
     }
 
-    private void setUserId(String userId)
+    private void setRegistrationDetails(EntityRegistrationDetails registrationDetails)
     {
-        this.userId = userId;
+        this.registrationDetails = registrationDetails;
     }
 
-    private void setPermLink(String permLink)
+    private void setDownloadLink(String downloadLink)
     {
-        this.permLink = permLink;
+        this.downloadLink = downloadLink;
     }
 
     @Override
@@ -387,10 +305,12 @@ public class Attachment implements Serializable, Comparable<Attachment>
     public String toString()
     {
         return "Attachment [fileName=" + fileName + ", version=" + version + ", title=" + title
-                + ", description=" + description + ", registrationDate=" + registrationDate
-                + ", userFirstName=" + userFirstName + ", userLastName=" + userLastName
-                + ", userEmail=" + userEmail + ", userId=" + userId + ", permLink=" + permLink
-                + "]";
+                + ", description=" + description + ", registrationDate="
+                + registrationDetails.getRegistrationDate() + ", userFirstName="
+                + registrationDetails.getUserFirstName() + ", userLastName="
+                + registrationDetails.getUserLastName() + ", userEmail="
+                + registrationDetails.getUserEmail() + ", userId="
+                + registrationDetails.getUserId() + ", downloadLink=" + downloadLink + "]";
     }
 
 }
diff --git a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/IProjectId.java b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/IProjectId.java
new file mode 100644
index 00000000000..7495c8e4ede
--- /dev/null
+++ b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/IProjectId.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 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.api.v1.dto.id.project;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.IObjectId;
+
+/**
+ * Holds information that uniquely identifies a project in openBIS.
+ *
+ * @author Franz-Josef Elmer
+ */
+@JsonObject("IProjectId")
+public interface IProjectId extends IObjectId
+{
+
+}
diff --git a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectIdentifierId.java b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectIdentifierId.java
new file mode 100644
index 00000000000..40d26b03015
--- /dev/null
+++ b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectIdentifierId.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 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.api.v1.dto.id.project;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.ObjectIdentifierId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
+
+/**
+ * Identifies a projec by identifier.
+ *
+ * @author Franz-Josef Elmer
+ */
+@JsonObject("ProjectIdentifierId")
+public class ProjectIdentifierId extends ObjectIdentifierId implements IProjectId
+{
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    /**
+     * @param identifier Project identifier, e.g. "/MY_SPACE/MY_PROJECT".
+     */
+    public ProjectIdentifierId(String identifier)
+    {
+        super(identifier);
+    }
+
+    //
+    // JSON-RPC
+    //
+
+    @SuppressWarnings("unused")
+    private ProjectIdentifierId()
+    {
+        super();
+    }
+
+
+}
diff --git a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectPermIdId.java b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectPermIdId.java
new file mode 100644
index 00000000000..c9ec0f8e3fe
--- /dev/null
+++ b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectPermIdId.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 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.api.v1.dto.id.project;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.ObjectPermIdId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
+
+/**
+ * Identifies a project by perm id.
+ *
+ * @author Franz-Josef Elmer
+ */
+@JsonObject("ProjectPermIdId")
+public class ProjectPermIdId extends ObjectPermIdId implements IProjectId
+{
+
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    /**
+     * @param permId Project perm id, e.g. "201108050937246-1031".
+     */
+    public ProjectPermIdId(String permId)
+    {
+        super(permId);
+    }
+
+    //
+    // JSON-RPC
+    //
+
+    @SuppressWarnings("unused")
+    private ProjectPermIdId()
+    {
+        super();
+    }
+
+
+}
diff --git a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectTechIdId.java b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectTechIdId.java
new file mode 100644
index 00000000000..25a4452099c
--- /dev/null
+++ b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/id/project/ProjectTechIdId.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 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.api.v1.dto.id.project;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.ObjectTechIdId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
+
+/**
+ * Identifies a project by tech id.
+ *
+ * @author Franz-Josef Elmer
+ */
+@JsonObject("ProjectTechIdId")
+public class ProjectTechIdId extends ObjectTechIdId implements IProjectId
+{
+
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    /**
+     * @param techId Project tech id.
+     */
+    public ProjectTechIdId(Long techId)
+    {
+        super(techId);
+    }
+
+    //
+    // JSON-RPC
+    //
+
+    @SuppressWarnings("unused")
+    private ProjectTechIdId()
+    {
+        super();
+    }
+
+
+}
-- 
GitLab