From 488884d3ba3b65f586759a922b3878e73667170e Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Thu, 31 Mar 2016 13:46:15 +0000
Subject: [PATCH] SSDM-3395 : V3 AS API - controlled vocabulary terms -
 mapVocabularyTerms

SVN: 36054
---
 .../server/asapi/v3/ApplicationServerApi.java |  13 ++
 .../asapi/v3/ApplicationServerApiLogger.java  |   8 +
 .../IMapVocabularyTermMethodExecutor.java     |  29 +++
 .../MapVocabularyTermMethodExecutor.java      |  77 +++++++
 .../asapi/v3/MapVocabularyTermTest.java       | 218 ++++++++++++++++++
 .../asapi/v3/SearchVocabularyTermTest.java    | 111 ++++-----
 .../asapi/v3/IApplicationServerApi.java       |   3 +
 .../generic/sharedapi/v3/dictionary.txt       |   3 +-
 8 files changed, 398 insertions(+), 64 deletions(-)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IMapVocabularyTermMethodExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/MapVocabularyTermMethodExecutor.java
 create mode 100644 openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/MapVocabularyTermTest.java

diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java
index 762bc7ea6c6..47c2079db25 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java
@@ -119,6 +119,7 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IMapMaterialM
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IMapProjectMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IMapSampleMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IMapSpaceMethodExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IMapVocabularyTermMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IRevertDeletionMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchCustomASServiceMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchDataSetMethodExecutor;
@@ -225,6 +226,9 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
     @Autowired
     private IMapMaterialMethodExecutor mapMaterialExecutor;
 
+    @Autowired
+    private IMapVocabularyTermMethodExecutor mapVocabularyTermExecutor;
+
     @Autowired
     private ISearchSpaceMethodExecutor searchSpaceExecutor;
 
@@ -517,6 +521,15 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
         return mapMaterialExecutor.map(sessionToken, materialIds, fetchOptions);
     }
 
+    @Override
+    @Transactional(readOnly = true)
+    @RolesAllowed({ RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    public Map<IVocabularyTermId, VocabularyTerm> mapVocabularyTerms(String sessionToken, List<? extends IVocabularyTermId> vocabularyTermIds,
+            VocabularyTermFetchOptions fetchOptions)
+    {
+        return mapVocabularyTermExecutor.map(sessionToken, vocabularyTermIds, fetchOptions);
+    }
+
     @Override
     @Transactional(readOnly = true)
     @RolesAllowed({ RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java
index 2ed497f426d..8da9010651f 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java
@@ -267,6 +267,14 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements
         return null;
     }
 
+    @Override
+    public Map<IVocabularyTermId, VocabularyTerm> mapVocabularyTerms(String sessionToken, List<? extends IVocabularyTermId> vocabularyTermIds,
+            VocabularyTermFetchOptions fetchOptions)
+    {
+        logAccess(sessionToken, "map-vocabulary-terms", "VOCABULARY_TERM_IDS(%s) FETCH_OPTIONS(%s)", abbreviate(vocabularyTermIds), fetchOptions);
+        return null;
+    }
+
     @Override
     public Map<IDataSetId, DataSet> mapDataSets(String sessionToken, List<? extends IDataSetId> dataSetIds, DataSetFetchOptions fetchOptions)
     {
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IMapVocabularyTermMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IMapVocabularyTermMethodExecutor.java
new file mode 100644
index 00000000000..b303ca2ef01
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IMapVocabularyTermMethodExecutor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 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.asapi.v3.executor.method;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyTermFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.IVocabularyTermId;
+
+/**
+ * @author pkupczyk
+ */
+public interface IMapVocabularyTermMethodExecutor extends IMapMethodExecutor<IVocabularyTermId, VocabularyTerm, VocabularyTermFetchOptions>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/MapVocabularyTermMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/MapVocabularyTermMethodExecutor.java
new file mode 100644
index 00000000000..8ac647572c1
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/MapVocabularyTermMethodExecutor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 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.asapi.v3.executor.method;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyTermFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.IVocabularyTermId;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.IMapObjectByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.vocabulary.IMapVocabularyTermByIdExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.ITranslator;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.entity.vocabulary.IVocabularyTermTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.dto.VocabularyTermPE;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class MapVocabularyTermMethodExecutor extends AbstractMapMethodExecutor<IVocabularyTermId, Long, VocabularyTerm, VocabularyTermFetchOptions>
+        implements IMapVocabularyTermMethodExecutor
+{
+
+    @Autowired
+    private IMapVocabularyTermByIdExecutor mapExecutor;
+
+    @Autowired
+    private IVocabularyTermTranslator translator;
+
+    @Override
+    protected IMapObjectByIdExecutor<IVocabularyTermId, Long> getMapExecutor()
+    {
+        return new IMapObjectByIdExecutor<IVocabularyTermId, Long>()
+            {
+                @Override
+                public Map<IVocabularyTermId, Long> map(IOperationContext context, Collection<? extends IVocabularyTermId> ids)
+                {
+                    Map<IVocabularyTermId, Long> idMap = new LinkedHashMap<IVocabularyTermId, Long>();
+                    Map<IVocabularyTermId, VocabularyTermPE> peMap = mapExecutor.map(context, ids);
+
+                    for (Map.Entry<IVocabularyTermId, VocabularyTermPE> entry : peMap.entrySet())
+                    {
+                        idMap.put(entry.getKey(), entry.getValue().getId());
+                    }
+
+                    return idMap;
+                }
+            };
+    }
+
+    @Override
+    protected ITranslator<Long, VocabularyTerm, VocabularyTermFetchOptions> getTranslator()
+    {
+        return translator;
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/MapVocabularyTermTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/MapVocabularyTermTest.java
new file mode 100644
index 00000000000..ef33013bbfe
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/MapVocabularyTermTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.asapi.v3;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyTermFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.IVocabularyTermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyTermPermId;
+
+/**
+ * @author pkupczyk
+ */
+@Test(groups = { "before remote api" })
+public class MapVocabularyTermTest extends AbstractVocabularyTermTest
+{
+
+    @Test
+    public void testMapByPermId()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermPermId permId1 = new VocabularyTermPermId("DOG", "ORGANISM");
+        VocabularyTermPermId permId2 = new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT");
+
+        Map<IVocabularyTermId, VocabularyTerm> map =
+                v3api.mapVocabularyTerms(sessionToken, Arrays.asList(permId1, permId2),
+                        new VocabularyTermFetchOptions());
+
+        assertEquals(2, map.size());
+
+        Iterator<VocabularyTerm> iter = map.values().iterator();
+        assertEquals(iter.next().getPermId(), permId1);
+        assertEquals(iter.next().getPermId(), permId2);
+
+        assertEquals(map.get(permId1).getPermId(), permId1);
+        assertEquals(map.get(permId2).getPermId(), permId2);
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test
+    public void testMapByPermIdCaseInsensitive()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermPermId permId1 = new VocabularyTermPermId("Dog", "OrGaNiSm");
+        VocabularyTermPermId permId2 = new VocabularyTermPermId("proPRIETARY", "$storage_FORMAT");
+
+        Map<IVocabularyTermId, VocabularyTerm> map =
+                v3api.mapVocabularyTerms(sessionToken, Arrays.asList(permId1, permId2),
+                        new VocabularyTermFetchOptions());
+
+        assertEquals(2, map.size());
+
+        Iterator<VocabularyTerm> iter = map.values().iterator();
+        assertEquals(iter.next().getPermId(), permId1);
+        assertEquals(iter.next().getPermId(), permId2);
+
+        assertEquals(map.get(permId1).getPermId().getCode(), "DOG");
+        assertEquals(map.get(permId1).getPermId().getVocabularyCode(), "ORGANISM");
+        assertEquals(map.get(new VocabularyTermPermId("DOG", "ORGANISM")).getPermId().getCode(), "DOG");
+        assertEquals(map.get(new VocabularyTermPermId("DOG", "ORGANISM")).getPermId().getVocabularyCode(), "ORGANISM");
+
+        assertEquals(map.get(permId2).getPermId().getCode(), "PROPRIETARY");
+        assertEquals(map.get(permId2).getPermId().getVocabularyCode(), "$STORAGE_FORMAT");
+        assertEquals(map.get(new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT")).getPermId().getCode(), "PROPRIETARY");
+        assertEquals(map.get(new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT")).getPermId().getVocabularyCode(), "$STORAGE_FORMAT");
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test
+    public void testMapByIdsNonexistent()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermPermId permId1 = new VocabularyTermPermId("DOG", "ORGANISM");
+        VocabularyTermPermId permId2 = new VocabularyTermPermId("IDONTEXIST", "MENEITHER");
+        VocabularyTermPermId permId3 = new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT");
+
+        Map<IVocabularyTermId, VocabularyTerm> map =
+                v3api.mapVocabularyTerms(sessionToken,
+                        Arrays.asList(permId1, permId2, permId3),
+                        new VocabularyTermFetchOptions());
+
+        assertEquals(2, map.size());
+
+        Iterator<VocabularyTerm> iter = map.values().iterator();
+        assertEquals(iter.next().getPermId(), permId1);
+        assertEquals(iter.next().getPermId(), permId3);
+
+        assertEquals(map.get(permId1).getPermId(), permId1);
+        assertEquals(map.get(permId3).getPermId(), permId3);
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test
+    public void testMapByIdsDuplicated()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermPermId permId1 = new VocabularyTermPermId("HUMAN", "ORGANISM");
+        VocabularyTermPermId permId2 = new VocabularyTermPermId("human", "organism");
+
+        Map<IVocabularyTermId, VocabularyTerm> map =
+                v3api.mapVocabularyTerms(sessionToken, Arrays.asList(permId1, permId2), new VocabularyTermFetchOptions());
+
+        assertEquals(1, map.size());
+
+        assertEquals(map.get(permId1).getPermId(), permId1);
+        assertEquals(map.get(permId2).getPermId(), permId2);
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test
+    public void testMapWithEmptyFetchOptions()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermPermId id = new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT");
+        VocabularyTermFetchOptions fetchOptions = new VocabularyTermFetchOptions();
+
+        Map<IVocabularyTermId, VocabularyTerm> terms =
+                v3api.mapVocabularyTerms(sessionToken, Arrays.asList(id), fetchOptions);
+
+        VocabularyTerm term = terms.get(id);
+        assertEquals(term.getPermId(), new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"));
+        assertEquals(term.getCode(), "PROPRIETARY");
+        assertEquals(term.getDescription(), "proprietary description");
+        assertEquals(term.getLabel(), "proprietary label");
+        assertEquals(term.getOrdinal(), Long.valueOf(1));
+        assertEquals(term.isOfficial(), Boolean.TRUE);
+        assertEqualsDate(term.getModificationDate(), "2008-11-05 09:18:00");
+        assertEqualsDate(term.getRegistrationDate(), "2008-11-05 09:18:00");
+        assertRegistratorNotFetched(term);
+        assertVocabularyNotFetched(term);
+    }
+
+    @Test
+    public void testMapWithVocabularyFetched()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermPermId id = new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT");
+        VocabularyTermFetchOptions fetchOptions = new VocabularyTermFetchOptions();
+        fetchOptions.withVocabulary().withRegistrator();
+
+        Map<IVocabularyTermId, VocabularyTerm> terms =
+                v3api.mapVocabularyTerms(sessionToken, Arrays.asList(id), fetchOptions);
+
+        VocabularyTerm term = terms.get(id);
+        assertEquals(term.getPermId(), new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"));
+        assertEquals(term.getCode(), "PROPRIETARY");
+        assertEquals(term.getDescription(), "proprietary description");
+        assertEquals(term.getLabel(), "proprietary label");
+        assertEquals(term.getOrdinal(), Long.valueOf(1));
+        assertEquals(term.isOfficial(), Boolean.TRUE);
+        assertEqualsDate(term.getModificationDate(), "2008-11-05 09:18:00");
+        assertEqualsDate(term.getRegistrationDate(), "2008-11-05 09:18:00");
+        assertRegistratorNotFetched(term);
+
+        assertEquals(term.getVocabulary().getCode(), "$STORAGE_FORMAT");
+        assertEquals(term.getVocabulary().getDescription(), "The on-disk storage format of a data set");
+        assertEqualsDate(term.getVocabulary().getRegistrationDate(), "2008-11-05 09:18:00");
+        assertEqualsDate(term.getVocabulary().getModificationDate(), "2009-03-23 15:34:44");
+        assertEquals(term.getVocabulary().getRegistrator().getUserId(), "system");
+    }
+
+    @Test
+    public void testMapWithRegistratorFetched()
+    {
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermPermId id = new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT");
+        VocabularyTermFetchOptions fetchOptions = new VocabularyTermFetchOptions();
+        fetchOptions.withRegistrator();
+
+        Map<IVocabularyTermId, VocabularyTerm> terms =
+                v3api.mapVocabularyTerms(sessionToken, Arrays.asList(id), fetchOptions);
+
+        VocabularyTerm term = terms.get(id);
+        assertEquals(term.getPermId(), new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"));
+        assertEquals(term.getCode(), "PROPRIETARY");
+        assertEquals(term.getDescription(), "proprietary description");
+        assertEquals(term.getLabel(), "proprietary label");
+        assertEquals(term.getOrdinal(), Long.valueOf(1));
+        assertEquals(term.isOfficial(), Boolean.TRUE);
+        assertEqualsDate(term.getModificationDate(), "2008-11-05 09:18:00");
+        assertEqualsDate(term.getRegistrationDate(), "2008-11-05 09:18:00");
+        assertEquals(term.getRegistrator().getUserId(), "system");
+        assertVocabularyNotFetched(term);
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchVocabularyTermTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchVocabularyTermTest.java
index ef341648a1e..7f4cbe119f9 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchVocabularyTermTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchVocabularyTermTest.java
@@ -35,69 +35,6 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularyTerm
 public class SearchVocabularyTermTest extends AbstractVocabularyTermTest
 {
 
-    @Test
-    public void testSearchWithEmptyFetchOptions()
-    {
-        VocabularyTermFetchOptions fetchOptions = new VocabularyTermFetchOptions();
-        VocabularyTerm term = searchTerm(new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"), fetchOptions);
-
-        assertEquals(term.getPermId(), new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"));
-        assertEquals(term.getCode(), "PROPRIETARY");
-        assertEquals(term.getDescription(), "proprietary description");
-        assertEquals(term.getLabel(), "proprietary label");
-        assertEquals(term.getOrdinal(), Long.valueOf(1));
-        assertEquals(term.isOfficial(), Boolean.TRUE);
-        assertEqualsDate(term.getModificationDate(), "2008-11-05 09:18:00");
-        assertEqualsDate(term.getRegistrationDate(), "2008-11-05 09:18:00");
-        assertRegistratorNotFetched(term);
-        assertVocabularyNotFetched(term);
-    }
-
-    @Test
-    public void testSearchWithVocabularyFetched()
-    {
-        VocabularyTermFetchOptions fetchOptions = new VocabularyTermFetchOptions();
-        fetchOptions.withVocabulary().withRegistrator();
-
-        VocabularyTerm term = searchTerm(new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"), fetchOptions);
-
-        assertEquals(term.getPermId(), new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"));
-        assertEquals(term.getCode(), "PROPRIETARY");
-        assertEquals(term.getDescription(), "proprietary description");
-        assertEquals(term.getLabel(), "proprietary label");
-        assertEquals(term.getOrdinal(), Long.valueOf(1));
-        assertEquals(term.isOfficial(), Boolean.TRUE);
-        assertEqualsDate(term.getModificationDate(), "2008-11-05 09:18:00");
-        assertEqualsDate(term.getRegistrationDate(), "2008-11-05 09:18:00");
-        assertRegistratorNotFetched(term);
-
-        assertEquals(term.getVocabulary().getCode(), "$STORAGE_FORMAT");
-        assertEquals(term.getVocabulary().getDescription(), "The on-disk storage format of a data set");
-        assertEqualsDate(term.getVocabulary().getRegistrationDate(), "2008-11-05 09:18:00");
-        assertEqualsDate(term.getVocabulary().getModificationDate(), "2009-03-23 15:34:44");
-        assertEquals(term.getVocabulary().getRegistrator().getUserId(), "system");
-    }
-
-    @Test
-    public void testSearchWithRegistratorFetched()
-    {
-        VocabularyTermFetchOptions fetchOptions = new VocabularyTermFetchOptions();
-        fetchOptions.withRegistrator();
-
-        VocabularyTerm term = searchTerm(new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"), fetchOptions);
-
-        assertEquals(term.getPermId(), new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"));
-        assertEquals(term.getCode(), "PROPRIETARY");
-        assertEquals(term.getDescription(), "proprietary description");
-        assertEquals(term.getLabel(), "proprietary label");
-        assertEquals(term.getOrdinal(), Long.valueOf(1));
-        assertEquals(term.isOfficial(), Boolean.TRUE);
-        assertEqualsDate(term.getModificationDate(), "2008-11-05 09:18:00");
-        assertEqualsDate(term.getRegistrationDate(), "2008-11-05 09:18:00");
-        assertEquals(term.getRegistrator().getUserId(), "system");
-        assertVocabularyNotFetched(term);
-    }
-
     @Test
     public void testSearchWithEmptyCriteria()
     {
@@ -223,6 +160,54 @@ public class SearchVocabularyTermTest extends AbstractVocabularyTermTest
                 new VocabularyTermPermId("PROPRIETARY", "$STORAGE_FORMAT"));
     }
 
+    @Test
+    public void testSearchWithSortingByCode()
+    {
+        VocabularyTermSearchCriteria criteria = new VocabularyTermSearchCriteria();
+        criteria.withOrOperator();
+        criteria.withId().thatEquals(new VocabularyTermPermId("MALE", "GENDER"));
+        criteria.withId().thatEquals(new VocabularyTermPermId("HUMAN", "ORGANISM"));
+        criteria.withId().thatEquals(new VocabularyTermPermId("DOG", "ORGANISM"));
+
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermFetchOptions fo = new VocabularyTermFetchOptions();
+
+        fo.sortBy().code().asc();
+        List<VocabularyTerm> terms1 = searchTerms(criteria, fo);
+        assertTerms(terms1, "DOG", "HUMAN", "MALE");
+
+        fo.sortBy().code().desc();
+        List<VocabularyTerm> terms2 = searchTerms(criteria, fo);
+        assertTerms(terms2, "MALE", "HUMAN", "DOG");
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test
+    public void testSearchWithSortingByOrdinal()
+    {
+        VocabularyTermSearchCriteria criteria = new VocabularyTermSearchCriteria();
+        criteria.withOrOperator();
+        criteria.withId().thatEquals(new VocabularyTermPermId("MALE", "GENDER"));
+        criteria.withId().thatEquals(new VocabularyTermPermId("HUMAN", "ORGANISM"));
+        criteria.withId().thatEquals(new VocabularyTermPermId("DOG", "ORGANISM"));
+
+        String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        VocabularyTermFetchOptions fo = new VocabularyTermFetchOptions();
+
+        fo.sortBy().ordinal().asc();
+        List<VocabularyTerm> terms1 = searchTerms(criteria, fo);
+        assertTerms(terms1, "MALE", "DOG", "HUMAN");
+
+        fo.sortBy().ordinal().desc();
+        List<VocabularyTerm> terms2 = searchTerms(criteria, fo);
+        assertTerms(terms2, "HUMAN", "DOG", "MALE");
+
+        v3api.logout(sessionToken);
+    }
+
     @Test
     public void testSearchWithAndOperator()
     {
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java
index a17f18a6267..2fe77b66253 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java
@@ -161,6 +161,9 @@ public interface IApplicationServerApi extends IRpcService
 
     public Map<IMaterialId, Material> mapMaterials(String sessionToken, List<? extends IMaterialId> materialIds, MaterialFetchOptions fetchOptions);
 
+    public Map<IVocabularyTermId, VocabularyTerm> mapVocabularyTerms(String sessionToken, List<? extends IVocabularyTermId> vocabularyTermIds,
+            VocabularyTermFetchOptions fetchOptions);
+
     public SearchResult<Space> searchSpaces(String sessionToken, SpaceSearchCriteria searchCriteria, SpaceFetchOptions fetchOptions);
 
     public SearchResult<Project> searchProjects(String sessionToken, ProjectSearchCriteria searchCriteria, ProjectFetchOptions fetchOptions);
diff --git a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
index 69194df39cc..5ba0ad322fb 100644
--- a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
+++ b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt
@@ -1059,4 +1059,5 @@ Vocabulary Search Criteria
 get Vocabulary Term Id
 set Vocabulary Term Id
 update Vocabulary Terms
-Vocabulary Term Update
\ No newline at end of file
+Vocabulary Term Update
+map Vocabulary Terms
\ No newline at end of file
-- 
GitLab