From c200aee6cd4f7d21db94e4f3d83f4e25bb621c64 Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Tue, 31 Mar 2015 07:05:40 +0000
Subject: [PATCH] SSDM-1677 : V3 AS API - finish up projects and materials -
 searchMaterials

SVN: 33765
---
 .../server/api/v3/ApplicationServerApi.java   |  26 ++
 .../api/v3/ApplicationServerApiLogger.java    |   8 +
 .../common/AbstractSearchObjectExecutor.java  |  76 +++++
 .../material/ISearchMaterialExecutor.java     |  29 ++
 .../material/SearchMaterialExecutor.java      |  49 +++
 .../EntityAttributeProviderFactory.java       |   3 +
 .../EntityTypeSearchCriterionTranslator.java  |   4 +
 .../search/MaterialAttributeProvider.java     |  69 +++++
 .../MaterialSearchCriterionTranslator.java    |  57 ++++
 .../SearchCriterionTranslatorFactory.java     |   1 +
 .../search/TagSearchCriterionTranslator.java  |   4 +
 .../search/detailed/IndexFieldNameHelper.java |   5 +
 .../dto/MaterialAttributeSearchFieldKind.java |   4 +
 .../systemtest/api/v3/AbstractTest.java       |  35 +++
 .../systemtest/api/v3/SearchMaterialTest.java | 280 ++++++++++++++++++
 .../shared/api/v3/IApplicationServerApi.java  |   3 +
 .../dto/search/MaterialSearchCriterion.java   |  53 ++++
 .../search/NumberFieldSearchCriterion.java    |   2 +-
 .../v3/dto/search/TechIdSearchCriterion.java  |  32 ++
 19 files changed, 739 insertions(+), 1 deletion(-)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/ISearchMaterialExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/SearchMaterialExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialAttributeProvider.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialSearchCriterionTranslator.java
 create mode 100644 openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/SearchMaterialTest.java
 create mode 100644 openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/MaterialSearchCriterion.java
 create mode 100644 openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/TechIdSearchCriterion.java

diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java
index c3dacc5ec38..3b5b4cd7890 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApi.java
@@ -42,6 +42,7 @@ import ch.ethz.sis.openbis.generic.server.api.v3.executor.experiment.IUpdateExpe
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.ICreateMaterialExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.IDeleteMaterialExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.IMapMaterialByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.ISearchMaterialExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.IUpdateMaterialExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.project.ICreateProjectExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.project.IMapProjectByIdExecutor;
@@ -115,6 +116,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.operation.IOperation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.operation.IOperationResult;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.DataSetSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ExperimentSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ProjectSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.SampleSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.SpaceSearchCriterion;
@@ -218,6 +220,9 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
     @Autowired
     private ISearchDataSetExecutor searchDataSetExecutor;
 
+    @Autowired
+    private ISearchMaterialExecutor searchMaterialExecutor;
+
     @Autowired
     private IDeleteSpaceExecutor deleteSpaceExecutor;
 
@@ -706,6 +711,27 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
         }
     }
 
+    @Override
+    @Transactional(readOnly = true)
+    @RolesAllowed({ RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    public List<Material> searchMaterials(String sessionToken, MaterialSearchCriterion searchCriterion, MaterialFetchOptions fetchOptions)
+    {
+        Session session = getSession(sessionToken);
+        OperationContext context = new OperationContext(session);
+
+        try
+        {
+            List<MaterialPE> materials = searchMaterialExecutor.search(context, searchCriterion);
+
+            Map<MaterialPE, Material> translatedMap =
+                    new MaterialTranslator(new TranslationContext(session, managedPropertyEvaluatorFactory), fetchOptions).translate(materials);
+            return new ArrayList<Material>(translatedMap.values());
+        } catch (Throwable t)
+        {
+            throw ExceptionUtils.create(context, t);
+        }
+    }
+
     @Override
     @Transactional
     @DatabaseCreateOrDeleteModification(value = { ObjectKind.SPACE, ObjectKind.DELETION })
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java
index cbb7e4e4783..60f7e52da1e 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/ApplicationServerApiLogger.java
@@ -66,6 +66,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.operation.IOperation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.operation.IOperationResult;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.DataSetSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ExperimentSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ProjectSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.SampleSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.SpaceSearchCriterion;
@@ -267,6 +268,13 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements
         return null;
     }
 
+    @Override
+    public List<Material> searchMaterials(String sessionToken, MaterialSearchCriterion searchCriterion, MaterialFetchOptions fetchOptions)
+    {
+        logAccess(sessionToken, "search-for-materials", "SEARCH_CRITERION:\n%s\nFETCH_OPTIONS:\n%s\n", searchCriterion, fetchOptions);
+        return null;
+    }
+
     @Override
     public void deleteSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceDeletionOptions deletionOptions)
     {
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/common/AbstractSearchObjectExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/common/AbstractSearchObjectExecutor.java
index d97f74b2ac6..9150dbf7ff1 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/common/AbstractSearchObjectExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/common/AbstractSearchObjectExecutor.java
@@ -33,6 +33,7 @@ import ch.ethz.sis.openbis.generic.server.api.v3.executor.IOperationContext;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.dataset.IMapDataSetByIdExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.entity.IMapEntityTypeByIdExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.experiment.IMapExperimentByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.IMapMaterialByIdExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.project.IMapProjectByIdExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.sample.IMapSampleByIdExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.space.IMapSpaceByIdExecutor;
@@ -46,6 +47,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.IObjectId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.IDataSetId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.entitytype.IEntityTypeId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.IExperimentId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.IMaterialId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.project.IProjectId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.ISampleId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.space.ISpaceId;
@@ -58,8 +60,10 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.EntityTypeSearchCrit
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ExperimentSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ISearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.IdSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.PermIdSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.SampleSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.TechIdSearchCriterion;
 import ch.systemsx.cisd.openbis.generic.server.ComponentNames;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ICommonBusinessObjectFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
@@ -67,6 +71,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.MetaprojectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
@@ -95,6 +100,9 @@ public abstract class AbstractSearchObjectExecutor<CRITERION extends AbstractObj
     @Autowired
     private IMapDataSetByIdExecutor mapDataSetByIdExecutor;
 
+    @Autowired
+    private IMapMaterialByIdExecutor mapMaterialByIdExecutor;
+
     @Autowired
     private IMapEntityTypeByIdExecutor mapEntityTypeByIdExecutor;
 
@@ -141,6 +149,9 @@ public abstract class AbstractSearchObjectExecutor<CRITERION extends AbstractObj
         replacers.add(new SampleTypeIdCriterionReplacer());
         replacers.add(new DataSetIdCriterionReplacer());
         replacers.add(new DataSetTypeIdCriterionReplacer());
+        replacers.add(new MaterialIdCriterionReplacer());
+        replacers.add(new MaterialPermIdCriterionReplacer());
+        replacers.add(new MaterialTypeIdCriterionReplacer());
         replacers.add(new TagIdCriterionReplacer());
 
         Map<ICriterionReplacer, Set<ISearchCriterion>> toReplaceMap = new HashMap<ICriterionReplacer, Set<ISearchCriterion>>();
@@ -437,6 +448,54 @@ public abstract class AbstractSearchObjectExecutor<CRITERION extends AbstractObj
 
     }
 
+    private class MaterialIdCriterionReplacer extends AbstractIdCriterionReplacer<IMaterialId, MaterialPE>
+    {
+
+        @Override
+        protected Class<IMaterialId> getIdClass()
+        {
+            return IMaterialId.class;
+        }
+
+        @Override
+        protected Map<IMaterialId, MaterialPE> getObjectMap(IOperationContext context, Collection<IMaterialId> materialIds)
+        {
+            return mapMaterialByIdExecutor.map(context, materialIds);
+        }
+
+        @Override
+        protected ISearchCriterion createReplacement(IOperationContext context, ISearchCriterion criterion, MaterialPE material)
+        {
+            TechIdSearchCriterion replacement = new TechIdSearchCriterion();
+            if (material == null)
+            {
+                replacement.thatEquals(-1);
+            } else
+            {
+                replacement.thatEquals(material.getId());
+            }
+            return replacement;
+        }
+
+    }
+
+    private class MaterialPermIdCriterionReplacer implements ICriterionReplacer
+    {
+
+        @Override
+        public boolean canReplace(IOperationContext context, Stack<ISearchCriterion> parentCriteria, ISearchCriterion criterion)
+        {
+            return criterion instanceof PermIdSearchCriterion && parentCriteria.peek() instanceof MaterialSearchCriterion;
+        }
+
+        @Override
+        public Map<ISearchCriterion, ISearchCriterion> replace(IOperationContext context, Collection<ISearchCriterion> criteria)
+        {
+            throw new UnsupportedOperationException("Please use criterion.withId().thatEquals(new MaterialPermId('CODE','TYPE')) instead.");
+        }
+
+    }
+
     private class TagIdCriterionReplacer extends AbstractIdCriterionReplacer<ITagId, MetaprojectPE>
     {
 
@@ -572,4 +631,21 @@ public abstract class AbstractSearchObjectExecutor<CRITERION extends AbstractObj
 
     }
 
+    private class MaterialTypeIdCriterionReplacer extends AbstractEntityTypeIdCriterionReplacer
+    {
+
+        @Override
+        protected EntityKind getEntityKind()
+        {
+            return EntityKind.MATERIAL;
+        }
+
+        @Override
+        protected Class<?> getEntityCriterionClass()
+        {
+            return MaterialSearchCriterion.class;
+        }
+
+    }
+
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/ISearchMaterialExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/ISearchMaterialExecutor.java
new file mode 100644
index 00000000000..571a6a238b9
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/ISearchMaterialExecutor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 ETH Zuerich, Scientific IT Services
+ *
+ * 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.ethz.sis.openbis.generic.server.api.v3.executor.material;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.common.ISearchObjectExecutor;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
+
+/**
+ * @author pkupczyk
+ */
+public interface ISearchMaterialExecutor extends ISearchObjectExecutor<MaterialSearchCriterion, MaterialPE>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/SearchMaterialExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/SearchMaterialExecutor.java
new file mode 100644
index 00000000000..6b464c875f7
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/material/SearchMaterialExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 ETH Zuerich, Scientific IT Services
+ *
+ * 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.ethz.sis.openbis.generic.server.api.v3.executor.material;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.executor.common.AbstractSearchObjectExecutor;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchAssociationCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class SearchMaterialExecutor extends AbstractSearchObjectExecutor<MaterialSearchCriterion, MaterialPE> implements
+        ISearchMaterialExecutor
+{
+
+    @Override
+    protected List<MaterialPE> doSearch(IOperationContext context, DetailedSearchCriteria criteria)
+    {
+        List<Long> materialIds = daoFactory.getHibernateSearchDAO().searchForEntityIds(context.getSession().tryGetPerson().getUserId(), criteria,
+                ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind.MATERIAL,
+                Collections.<DetailedSearchAssociationCriteria> emptyList());
+
+        return daoFactory.getMaterialDAO().listMaterialsById(materialIds);
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityAttributeProviderFactory.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityAttributeProviderFactory.java
index bb9e64035f6..3a4da20aab7 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityAttributeProviderFactory.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityAttributeProviderFactory.java
@@ -36,6 +36,9 @@ public class EntityAttributeProviderFactory implements IEntityAttributeProviderF
         } else if (EntityKind.DATA_SET.equals(entityKind))
         {
             return new DataSetAttributeProvider();
+        } else if (EntityKind.MATERIAL.equals(entityKind))
+        {
+            return new MaterialAttributeProvider();
         } else
         {
             throw new IllegalArgumentException("Could not create entity attribute provider for unknown entity kind: " + entityKind);
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityTypeSearchCriterionTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityTypeSearchCriterionTranslator.java
index 7fdcfbacc4e..772abcd9ff7 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityTypeSearchCriterionTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/EntityTypeSearchCriterionTranslator.java
@@ -22,6 +22,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchField;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentAttributeSearchFieldKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleAttributeSearchFieldKind;
 
 /**
@@ -53,6 +54,9 @@ public class EntityTypeSearchCriterionTranslator extends AbstractFieldFromCompos
         } else if (EntityKind.SAMPLE.equals(entityKind))
         {
             return DetailedSearchField.createAttributeField(SampleAttributeSearchFieldKind.SAMPLE_TYPE);
+        } else if (EntityKind.MATERIAL.equals(entityKind))
+        {
+            return DetailedSearchField.createAttributeField(MaterialAttributeSearchFieldKind.MATERIAL_TYPE);
         } else
         {
             throw new IllegalArgumentException("Unknown entity kind: " + entityKind);
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialAttributeProvider.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialAttributeProvider.java
new file mode 100644
index 00000000000..1ba5b437c77
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialAttributeProvider.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 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.ethz.sis.openbis.generic.server.api.v3.translator.search;
+
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.ID;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.CODE;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.PERM_ID;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.MODIFICATION_DATE;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.MODIFICATION_DATE_FROM;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.MODIFICATION_DATE_UNTIL;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.REGISTRATION_DATE;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.REGISTRATION_DATE_FROM;
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind.REGISTRATION_DATE_UNTIL;
+
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.CodeSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ISearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ModificationDateSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.PermIdSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.RegistrationDateSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.TechIdSearchCriterion;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IAttributeSearchFieldKind;
+
+/**
+ * @author pkupczyk
+ */
+public class MaterialAttributeProvider extends AbstractEntityAttributeProvider
+{
+
+    @Override
+    public IAttributeSearchFieldKind getAttribute(ISearchCriterion criterion)
+    {
+        if (criterion instanceof TechIdSearchCriterion)
+        {
+            return ID;
+        } else if (criterion instanceof CodeSearchCriterion)
+        {
+            return CODE;
+        } else if (criterion instanceof PermIdSearchCriterion)
+        {
+            return PERM_ID;
+        } else if (criterion instanceof RegistrationDateSearchCriterion)
+        {
+            RegistrationDateSearchCriterion dateCriterion = (RegistrationDateSearchCriterion) criterion;
+            return getDateAttribute(dateCriterion.getFieldValue(), REGISTRATION_DATE, REGISTRATION_DATE_UNTIL, REGISTRATION_DATE_FROM);
+        } else if (criterion instanceof ModificationDateSearchCriterion)
+        {
+            ModificationDateSearchCriterion dateCriterion = (ModificationDateSearchCriterion) criterion;
+            return getDateAttribute(dateCriterion.getFieldValue(), MODIFICATION_DATE, MODIFICATION_DATE_UNTIL, MODIFICATION_DATE_FROM);
+        } else
+        {
+            throw new IllegalArgumentException("Unknown attribute criterion: " + criterion);
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialSearchCriterionTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialSearchCriterionTranslator.java
new file mode 100644
index 00000000000..f2d757920ad
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/MaterialSearchCriterionTranslator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 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.ethz.sis.openbis.generic.server.api.v3.translator.search;
+
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ISearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
+
+/**
+ * @author pkupczyk
+ */
+public class MaterialSearchCriterionTranslator extends AbstractCompositeSearchCriterionTranslator
+{
+
+    protected MaterialSearchCriterionTranslator(IDAOFactory idaoFactory, IEntityAttributeProviderFactory entityAttributeProviderFactory)
+    {
+        super(idaoFactory, entityAttributeProviderFactory);
+    }
+
+    @Override
+    protected boolean doAccepts(ISearchCriterion criterion)
+    {
+        return criterion instanceof MaterialSearchCriterion;
+    }
+
+    @Override
+    protected SearchCriterionTranslationResult doTranslate(SearchTranslationContext context, ISearchCriterion criterion)
+    {
+        context.pushEntityKind(EntityKind.MATERIAL);
+        SearchCriterionTranslationResult translationResult = super.doTranslate(context, criterion);
+        context.popEntityKind();
+
+        if (context.peekEntityKind() == null)
+        {
+            return translationResult;
+        } else
+        {
+            throw new IllegalArgumentException("Material criterion cannot be nested inside another criterion");
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/SearchCriterionTranslatorFactory.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/SearchCriterionTranslatorFactory.java
index 76b331c8792..11cd42346fc 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/SearchCriterionTranslatorFactory.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/SearchCriterionTranslatorFactory.java
@@ -51,6 +51,7 @@ public class SearchCriterionTranslatorFactory extends AbstractSearchCriterionTra
         translators.add(new ExperimentSearchCriterionTranslator(getDaoFactory(), getEntityAttributeProviderFactory()));
         translators.add(new SampleSearchCriterionTranslator(getDaoFactory(), getEntityAttributeProviderFactory()));
         translators.add(new DataSetSearchCriterionTranslator(getDaoFactory(), getEntityAttributeProviderFactory()));
+        translators.add(new MaterialSearchCriterionTranslator(getDaoFactory(), getEntityAttributeProviderFactory()));
         return translators;
     }
 
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/TagSearchCriterionTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/TagSearchCriterionTranslator.java
index ff1c22cb152..6749ba2b188 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/TagSearchCriterionTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/search/TagSearchCriterionTranslator.java
@@ -22,6 +22,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchField;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentAttributeSearchFieldKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialAttributeSearchFieldKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleAttributeSearchFieldKind;
 
 /**
@@ -52,6 +53,9 @@ public class TagSearchCriterionTranslator extends AbstractFieldFromCompositeSear
         } else if (EntityKind.SAMPLE.equals(entityKind))
         {
             return DetailedSearchField.createAttributeField(SampleAttributeSearchFieldKind.METAPROJECT);
+        } else if (EntityKind.MATERIAL.equals(entityKind))
+        {
+            return DetailedSearchField.createAttributeField(MaterialAttributeSearchFieldKind.METAPROJECT);
         } else
         {
             throw new IllegalArgumentException("Unknown entity kind: " + entityKind);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/detailed/IndexFieldNameHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/detailed/IndexFieldNameHelper.java
index a8496b1a09e..74d34d5fa27 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/detailed/IndexFieldNameHelper.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/search/detailed/IndexFieldNameHelper.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.openbis.generic.server.dataaccess.db.search.detailed;
 
+import static ch.systemsx.cisd.openbis.generic.shared.dto.hibernate.SearchFieldConstants.ID;
 import static ch.systemsx.cisd.openbis.generic.shared.dto.hibernate.SearchFieldConstants.CODE;
 import static ch.systemsx.cisd.openbis.generic.shared.dto.hibernate.SearchFieldConstants.PERM_ID;
 
@@ -179,6 +180,10 @@ class IndexFieldNameHelper
     {
         switch (attributeKind)
         {
+            case ID:
+                return ID;
+            case PERM_ID:
+                return PERM_ID;
             case CODE:
                 return CODE;
             case MATERIAL_TYPE:
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/MaterialAttributeSearchFieldKind.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/MaterialAttributeSearchFieldKind.java
index 941684bd572..96fb5a2b50f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/MaterialAttributeSearchFieldKind.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/MaterialAttributeSearchFieldKind.java
@@ -25,7 +25,11 @@ import java.io.Serializable;
  */
 public enum MaterialAttributeSearchFieldKind implements Serializable, IAttributeSearchFieldKind
 {
+    ID("Id"),
+
     CODE("Code"),
+    
+    PERM_ID("Perm Id"),
 
     MATERIAL_TYPE("Material Type"),
 
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/AbstractTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/AbstractTest.java
index 1ddee1d890b..d1e1da05cf2 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/AbstractTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/AbstractTest.java
@@ -50,11 +50,13 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.interfaces.IProperti
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.interfaces.IRegistratorHolder;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.interfaces.ISpaceHolder;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.interfaces.ITagsHolder;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.project.Project;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.sample.Sample;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.space.Space;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.tag.Tag;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.IObjectId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.MaterialPermId;
 import ch.ethz.sis.openbis.generic.shared.api.v3.exceptions.NotFetchedException;
 import ch.ethz.sis.openbis.generic.shared.api.v3.exceptions.ObjectNotFoundException;
 import ch.ethz.sis.openbis.generic.shared.api.v3.exceptions.UnauthorizedObjectAccessException;
@@ -433,6 +435,28 @@ public class AbstractTest extends SystemTestCase
         }
     }
 
+    protected void assertRuntimeException(IDelegatedAction action, String expectedMessage)
+    {
+        assertRuntimeException(action, expectedMessage, null);
+    }
+
+    protected void assertRuntimeException(IDelegatedAction action, String expectedMessage, String expectedContextDescription)
+    {
+        try
+        {
+            action.execute();
+            fail("Expected an exception to be thrown");
+        } catch (Exception e)
+        {
+            assertEquals(e.getClass(), RuntimeException.class);
+            AssertionUtil.assertContains(expectedMessage, e.getMessage());
+            if (expectedContextDescription != null)
+            {
+                AssertionUtil.assertContains("(Context: [" + expectedContextDescription + "])", e.getMessage());
+            }
+        }
+    }
+
     protected void assertUserFailureException(IDelegatedAction action, String expectedMessage)
     {
         assertUserFailureException(action, expectedMessage, null);
@@ -641,4 +665,15 @@ public class AbstractTest extends SystemTestCase
         assertCollectionContainsOnly(actualSet, expectedIdentifiers);
     }
 
+    protected static void assertMaterialPermIds(Collection<Material> materials, MaterialPermId... expectedPermIds)
+    {
+        Set<MaterialPermId> actualSet = new HashSet<MaterialPermId>();
+        for (Material material : materials)
+        {
+            actualSet.add(material.getPermId());
+        }
+
+        assertCollectionContainsOnly(actualSet, expectedPermIds);
+    }
+
 }
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/SearchMaterialTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/SearchMaterialTest.java
new file mode 100644
index 00000000000..5b0a5a74027
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/SearchMaterialTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2014 ETH Zuerich, Scientific IT Services
+ *
+ * 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.ethz.sis.openbis.systemtest.api.v3;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.material.MaterialFetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.entitytype.EntityTypePermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.MaterialPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.tag.TagPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
+import ch.systemsx.cisd.common.action.IDelegatedAction;
+
+/**
+ * @author pkupczyk
+ */
+public class SearchMaterialTest extends AbstractTest
+{
+
+    @Test
+    public void testSearchWithIdSetToPermId()
+    {
+        MaterialPermId permId = new MaterialPermId("VIRUS1", "VIRUS");
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withId().thatEquals(permId);
+        testSearch(TEST_USER, criterion, permId);
+    }
+
+    @Test
+    public void testSearchWithPermId()
+    {
+        final MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withPermId().thatEquals("NOT SUPPORTED YET");
+        assertRuntimeException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    testSearch(TEST_USER, criterion);
+                }
+            }, "Please use criterion.withId().thatEquals(new MaterialPermId('CODE','TYPE')) instead.");
+    }
+
+    @Test
+    public void testSearchWithCode()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withCode().thatStartsWith("VIRUS");
+        testSearch(TEST_USER, criterion, new MaterialPermId("VIRUS1", "VIRUS"), new MaterialPermId("VIRUS2", "VIRUS"));
+    }
+
+    @Test
+    public void testSearchWithTypeWithIdSetToPermId()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withType().withId().thatEquals(new EntityTypePermId("BACTERIUM"));
+        testSearch(TEST_USER, criterion, new MaterialPermId("BACTERIUM1", "BACTERIUM"), new MaterialPermId("BACTERIUM2", "BACTERIUM"),
+                new MaterialPermId("BACTERIUM-X", "BACTERIUM"), new MaterialPermId("BACTERIUM-Y", "BACTERIUM"));
+    }
+
+    @Test
+    public void testSearchWithTypeWithCode()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withType().withCode().thatEquals("BACTERIUM");
+        testSearch(TEST_USER, criterion, new MaterialPermId("BACTERIUM1", "BACTERIUM"), new MaterialPermId("BACTERIUM2", "BACTERIUM"),
+                new MaterialPermId("BACTERIUM-X", "BACTERIUM"), new MaterialPermId("BACTERIUM-Y", "BACTERIUM"));
+    }
+
+    @Test
+    public void testSearchWithTypeWithPermId()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withType().withPermId().thatEquals("BACTERIUM");
+        testSearch(TEST_USER, criterion, new MaterialPermId("BACTERIUM1", "BACTERIUM"), new MaterialPermId("BACTERIUM2", "BACTERIUM"),
+                new MaterialPermId("BACTERIUM-X", "BACTERIUM"), new MaterialPermId("BACTERIUM-Y", "BACTERIUM"));
+    }
+
+    @Test
+    public void testSearchWithPropertyThatEquals()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEquals("adenovirus");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"), new MaterialPermId("AD5", "VIRUS"));
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEquals("adenoviru");
+        testSearch(TEST_USER, criterion, 0);
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEquals("denoviru");
+        testSearch(TEST_USER, criterion, 0);
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEquals("denovirus");
+        testSearch(TEST_USER, criterion, 0);
+    }
+
+    @Test
+    public void testSearchWithPropertyThatStartsWith()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatStartsWith("adenovirus");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"), new MaterialPermId("AD5", "VIRUS"));
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatStartsWith("adenoviru");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"), new MaterialPermId("AD5", "VIRUS"));
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatStartsWith("denoviru");
+        testSearch(TEST_USER, criterion, 0);
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatStartsWith("denovirus");
+        testSearch(TEST_USER, criterion, 0);
+    }
+
+    @Test
+    public void testSearchWithPropertyThatEndsWith()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEndsWith("adenovirus");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"), new MaterialPermId("AD5", "VIRUS"));
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEndsWith("adenoviru");
+        testSearch(TEST_USER, criterion, 0);
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEndsWith("denoviru");
+        testSearch(TEST_USER, criterion, 0);
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatEndsWith("denovirus");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"), new MaterialPermId("AD5", "VIRUS"));
+    }
+
+    @Test
+    public void testSearchWithPropertyThatContains()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatContains("adenovirus");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"), new MaterialPermId("AD5", "VIRUS"));
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION").thatContains("denoviru");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"), new MaterialPermId("AD5", "VIRUS"));
+    }
+
+    @Test
+    public void testSearchWithProperty()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withProperty("DESCRIPTION");
+        testSearch(TEST_USER, criterion, 40);
+    }
+
+    @Test
+    public void testSearchWithDateProperty()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withDateProperty("PURCHASE_DATE").withTimeZone(0).thatEquals("2007-07-17");
+        testSearch(TEST_USER, criterion, new MaterialPermId("NEUTRAL", "CONTROL"), new MaterialPermId("C-NO-SEC", "CONTROL"), new MaterialPermId(
+                "INHIBITOR", "CONTROL"));
+    }
+
+    @Test
+    public void testSearchWithAnyProperty()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withAnyProperty().thatEquals("HUHU");
+        testSearch(TEST_USER, criterion, new MaterialPermId("MYGENE1", "GENE"));
+
+        criterion = new MaterialSearchCriterion();
+        criterion.withAnyProperty().thatEquals("HUH");
+        testSearch(TEST_USER, criterion, 0);
+    }
+
+    public void testSearchWithAnyField()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withAnyField().thatEquals("MYGENE2");
+        testSearch(TEST_USER, criterion, new MaterialPermId("MYGENE2", "GENE"));
+    }
+
+    @Test
+    public void testSearchWithTagWithIdSetToPermId()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withTag().withId().thatEquals(new TagPermId("/test/TEST_METAPROJECTS"));
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"));
+    }
+
+    @Test
+    public void testSearchWithTagWithCode()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withTag().withCode().thatEquals("TEST_METAPROJECTS");
+        testSearch(TEST_USER, criterion, new MaterialPermId("AD3", "VIRUS"));
+    }
+
+    @Test
+    public void testSearchWithRegistrationDate()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withRegistrationDate().thatEquals("2012-03-13");
+        testSearch(TEST_USER, criterion, new MaterialPermId("SRM_1", "SELF_REF"), new MaterialPermId("SRM_1A", "SELF_REF"));
+    }
+
+    @Test
+    public void testSearchWithModificationDate()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withModificationDate().thatEquals("2012-03-13");
+        testSearch(TEST_USER, criterion, new MaterialPermId("SRM_1", "SELF_REF"), new MaterialPermId("SRM_1A", "SELF_REF"));
+    }
+
+    @Test
+    public void testSearchWithAndOperator()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withAndOperator();
+        criterion.withCode().thatContains("SRM");
+        criterion.withCode().thatContains("1A");
+        testSearch(TEST_USER, criterion, new MaterialPermId("SRM_1A", "SELF_REF"));
+    }
+
+    @Test
+    public void testSearchWithOrOperator()
+    {
+        MaterialSearchCriterion criterion = new MaterialSearchCriterion();
+        criterion.withOrOperator();
+        criterion.withCode().thatEquals("SRM_1");
+        criterion.withCode().thatEquals("SRM_1A");
+        testSearch(TEST_USER, criterion, new MaterialPermId("SRM_1", "SELF_REF"), new MaterialPermId("SRM_1A", "SELF_REF"));
+    }
+
+    private void testSearch(String user, MaterialSearchCriterion criterion, MaterialPermId... expectedPermIds)
+    {
+        String sessionToken = v3api.login(user, PASSWORD);
+
+        List<Material> materials =
+                v3api.searchMaterials(sessionToken, criterion, new MaterialFetchOptions());
+
+        assertMaterialPermIds(materials, expectedPermIds);
+        v3api.logout(sessionToken);
+    }
+
+    private void testSearch(String user, MaterialSearchCriterion criterion, int expectedCount)
+    {
+        String sessionToken = v3api.login(user, PASSWORD);
+
+        List<Material> materials =
+                v3api.searchMaterials(sessionToken, criterion, new MaterialFetchOptions());
+
+        assertEquals(materials.size(), expectedCount);
+        v3api.logout(sessionToken);
+    }
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/IApplicationServerApi.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/IApplicationServerApi.java
index 05716d0c42b..fc96f28ee6e 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/IApplicationServerApi.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/IApplicationServerApi.java
@@ -65,6 +65,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.operation.IOperation;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.operation.IOperationResult;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.DataSetSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ExperimentSearchCriterion;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.ProjectSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.SampleSearchCriterion;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.SpaceSearchCriterion;
@@ -196,6 +197,8 @@ public interface IApplicationServerApi extends IRpcService
 
     public List<DataSet> searchDataSets(String sessionToken, DataSetSearchCriterion searchCriterion, DataSetFetchOptions fetchOptions);
 
+    public List<Material> searchMaterials(String sessionToken, MaterialSearchCriterion searchCriterion, MaterialFetchOptions fetchOptions);
+
     public void deleteSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceDeletionOptions deletionOptions);
 
     // REPLACES:
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/MaterialSearchCriterion.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/MaterialSearchCriterion.java
new file mode 100644
index 00000000000..dac83177217
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/MaterialSearchCriterion.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 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.ethz.sis.openbis.generic.shared.api.v3.dto.search;
+
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.material.IMaterialId;
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author pkupczyk
+ */
+@JsonObject("dto.search.MaterialSearchCriterion")
+public class MaterialSearchCriterion extends AbstractEntitySearchCriterion<IMaterialId>
+{
+
+    private static final long serialVersionUID = 1L;
+
+    public MaterialSearchCriterion()
+    {
+    }
+
+    public MaterialSearchCriterion withOrOperator()
+    {
+        return (MaterialSearchCriterion) withOperator(SearchOperator.OR);
+    }
+
+    public MaterialSearchCriterion withAndOperator()
+    {
+        return (MaterialSearchCriterion) withOperator(SearchOperator.AND);
+    }
+
+    @Override
+    protected SearchCriterionToStringBuilder createBuilder()
+    {
+        SearchCriterionToStringBuilder builder = super.createBuilder();
+        builder.setName("MATERIAL");
+        return builder;
+    }
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/NumberFieldSearchCriterion.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/NumberFieldSearchCriterion.java
index 8be6c91f307..d9f3c37b2ce 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/NumberFieldSearchCriterion.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/NumberFieldSearchCriterion.java
@@ -29,7 +29,7 @@ public class NumberFieldSearchCriterion extends AbstractFieldSearchCriterion<Abs
         super(fieldName, fieldType);
     }
 
-    public void equalTo(Number number)
+    public void thatEquals(Number number)
     {
         setFieldValue(new NumberEqualToValue(number));
     }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/TechIdSearchCriterion.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/TechIdSearchCriterion.java
new file mode 100644
index 00000000000..7f4ee1d8eb3
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/search/TechIdSearchCriterion.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 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.ethz.sis.openbis.generic.shared.api.v3.dto.search;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+@JsonObject("dto.search.TechIdSearchCriterion")
+public class TechIdSearchCriterion extends NumberFieldSearchCriterion
+{
+
+    private static final long serialVersionUID = 1L;
+
+    public TechIdSearchCriterion()
+    {
+        super("id", SearchFieldType.ATTRIBUTE);
+    }
+
+}
\ No newline at end of file
-- 
GitLab