From 410504994e047b14cff6d5d8cdcd8fceace14b8e Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Wed, 20 Apr 2016 08:20:31 +0000
Subject: [PATCH] SSDM-3476 : V3 AS API - tags: deleteTags and searchTags
 methods

SVN: 36234
---
 .../openbis-v3-api-test/html/test/common.js   |   8 +
 .../openbis-v3-api-test/html/test/dtos.js     |   1 +
 .../html/test/test-delete.js                  |  90 ++++++----
 .../html/test/test-search.js                  |  21 ++-
 .../server/asapi/v3/ApplicationServerApi.java |  30 +++-
 .../asapi/v3/ApplicationServerApiLogger.java  |  15 ++
 .../method/DeleteTagMethodExecutor.java       |  44 +++++
 .../method/IDeleteTagMethodExecutor.java      |  28 +++
 .../method/ISearchTagMethodExecutor.java      |  29 ++++
 .../method/SearchTagMethodExecutor.java       |  60 +++++++
 .../v3/executor/tag/DeleteTagExecutor.java    |  76 ++++++++
 .../v3/executor/tag/IDeleteTagExecutor.java   |  29 ++++
 .../v3/executor/tag/ISearchTagExecutor.java   |  29 ++++
 .../v3/executor/tag/MapTagByIdExecutor.java   |  15 +-
 .../v3/executor/tag/SearchTagExecutor.java    | 102 +++++++++++
 .../search/AbstractCompositeSearchCriteria.js |   2 +
 .../as/dto/tag/delete/TagDeletionOptions.js   |  13 ++
 .../public/resources/api/v3/openbis.js        |  25 ++-
 .../systemtest/asapi/v3/DeleteTagTest.java    | 164 ++++++++++++++++++
 .../systemtest/asapi/v3/SearchTagTest.java    | 162 +++++++++++++++++
 .../asapi/v3/IApplicationServerApi.java       |   6 +
 .../dto/tag/deletion/TagDeletionOptions.java  |  31 ++++
 .../v3/dto/tag/search/TagSearchCriteria.java  |  11 ++
 .../generic/sharedapi/v3/dictionary.txt       |   5 +-
 24 files changed, 951 insertions(+), 45 deletions(-)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/DeleteTagMethodExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IDeleteTagMethodExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/ISearchTagMethodExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/SearchTagMethodExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/DeleteTagExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/IDeleteTagExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/ISearchTagExecutor.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/SearchTagExecutor.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/tag/delete/TagDeletionOptions.js
 create mode 100644 openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/DeleteTagTest.java
 create mode 100644 openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchTagTest.java
 create mode 100644 openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/deletion/TagDeletionOptions.java

diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js
index 26993ffec79..bc7a290b9ea 100644
--- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js
+++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js
@@ -41,6 +41,7 @@ define([ 'jquery', 'openbis', 'underscore', 'test/dtos' ], function($, openbis,
 		this.DataSetDeletionOptions = dtos.DataSetDeletionOptions;
 		this.MaterialDeletionOptions = dtos.MaterialDeletionOptions;
 		this.VocabularyTermDeletionOptions = dtos.VocabularyTermDeletionOptions;
+		this.TagDeletionOptions = dtos.TagDeletionOptions;
 		this.EntityTypePermId = dtos.EntityTypePermId;
 		this.EntityTypeSearchCriteria = dtos.EntityTypeSearchCriteria;
 		this.SpacePermId = dtos.SpacePermId;
@@ -309,6 +310,13 @@ define([ 'jquery', 'openbis', 'underscore', 'test/dtos' ], function($, openbis,
 			return facade.deleteVocabularyTerms([ id ], options);
 		}.bind(this);
 
+		this.deleteTag = function(facade, id) {
+			var c = this;
+			var options = new dtos.TagDeletionOptions();
+			options.setReason("test reason");
+			return facade.deleteTags([ id ], options);
+		}.bind(this);
+
 		this.getObjectProperty = function(object, propertyName) {
 			var propertyNames = propertyName.split('.');
 			for ( var pn in propertyNames) {
diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js
index 37c38bf22fb..22d224b0028 100644
--- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js
+++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js
@@ -296,6 +296,7 @@ var sources = [
 	'as/dto/tag/Tag',
 	'as/dto/tag/update/TagUpdate',
 	'as/dto/tag/create/TagCreation',
+   	'as/dto/tag/delete/TagDeletionOptions',
 	'as/dto/vocabulary/fetchoptions/VocabularyFetchOptions',
 	'as/dto/vocabulary/fetchoptions/VocabularySortOptions',
 	'as/dto/vocabulary/fetchoptions/VocabularyTermFetchOptions',
diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-delete.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-delete.js
index 9c6b4b55770..729c282be4b 100644
--- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-delete.js
+++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-delete.js
@@ -72,38 +72,53 @@ define([ 'jquery', 'underscore', 'openbis', 'test/common' ], function($, _, open
 		var testDeleteWithTrashAndConfirm = function(c, fCreate, fFind, fDelete) {
 			c.start();
 
-			c.createFacadeAndLogin().then(function(facade) {
-				return fCreate(facade).then(function(permId) {
-					c.assertNotNull(permId, "Entity was created");
-					return fFind(facade, permId).then(function(entity) {
-						c.assertNotNull(entity, "Entity can be found");
-						return facade.searchDeletions(new c.DeletionSearchCriteria(), new c.DeletionFetchOptions()).then(function(deletionsBeforeDeletion) {
-							c.ok("Got before deletions");
-							return fDelete(facade, permId).then(function(deletionId) {
-								c.ok("Entity was deleted");
-								return facade.searchDeletions(new c.DeletionSearchCriteria(), new c.DeletionFetchOptions()).then(function(deletionsAfterDeletion) {
-									c.ok("Got after deletions");
-									c.assertEqual(deletionsAfterDeletion.getObjects().length, deletionsBeforeDeletion.getObjects().length + 1, "One new deletion");
-									c.assertEqual(deletionsAfterDeletion.getObjects()[deletionsAfterDeletion.getObjects().length - 1].getId().getTechId(), deletionId.getTechId(), "Deletion ids match");
-									return fFind(facade, permId).then(function(entityAfterDeletion) {
-										c.assertNull(entityAfterDeletion, "Entity was deleted");
-										return facade.confirmDeletions([ deletionId ]).then(function() {
-											c.ok("Confirmed deletion");
-											return fFind(facade, permId).then(function(entityAfterConfirm) {
-												c.assertNull(entityAfterConfirm, "Entity is still gone");
-												return facade.searchDeletions(new c.DeletionSearchCriteria(), new c.DeletionFetchOptions()).then(function(deletionsAfterConfirm) {
-													c.assertEqual(deletionsAfterConfirm.getObjects().length, deletionsBeforeDeletion.getObjects().length, "New deletion is also gone");
-													c.finish();
-												});
+			c.createFacadeAndLogin().then(
+					function(facade) {
+						return fCreate(facade).then(
+								function(permId) {
+									c.assertNotNull(permId, "Entity was created");
+									return fFind(facade, permId).then(
+											function(entity) {
+												c.assertNotNull(entity, "Entity can be found");
+												return facade.searchDeletions(new c.DeletionSearchCriteria(), new c.DeletionFetchOptions()).then(
+														function(deletionsBeforeDeletion) {
+															c.ok("Got before deletions");
+															return fDelete(facade, permId).then(
+																	function(deletionId) {
+																		c.ok("Entity was deleted");
+																		return facade.searchDeletions(new c.DeletionSearchCriteria(), new c.DeletionFetchOptions()).then(
+																				function(deletionsAfterDeletion) {
+																					c.ok("Got after deletions");
+																					c.assertEqual(deletionsAfterDeletion.getObjects().length, deletionsBeforeDeletion.getObjects().length + 1,
+																							"One new deletion");
+																					c.assertEqual(deletionsAfterDeletion.getObjects()[deletionsAfterDeletion.getObjects().length - 1].getId()
+																							.getTechId(), deletionId.getTechId(), "Deletion ids match");
+																					return fFind(facade, permId).then(
+																							function(entityAfterDeletion) {
+																								c.assertNull(entityAfterDeletion, "Entity was deleted");
+																								return facade.confirmDeletions([ deletionId ]).then(
+																										function() {
+																											c.ok("Confirmed deletion");
+																											return fFind(facade, permId).then(
+																													function(entityAfterConfirm) {
+																														c.assertNull(entityAfterConfirm, "Entity is still gone");
+																														return facade.searchDeletions(new c.DeletionSearchCriteria(),
+																																new c.DeletionFetchOptions()).then(
+																																function(deletionsAfterConfirm) {
+																																	c.assertEqual(deletionsAfterConfirm.getObjects().length,
+																																			deletionsBeforeDeletion.getObjects().length,
+																																			"New deletion is also gone");
+																																	c.finish();
+																																});
+																													});
+																										});
+																							});
+																				});
+																	});
+														});
 											});
-										});
-									});
 								});
-							});
-						});
-					});
-				});
-			}).fail(function(error) {
+					}).fail(function(error) {
 				c.fail(error.message);
 				c.finish();
 			});
@@ -153,16 +168,21 @@ define([ 'jquery', 'underscore', 'openbis', 'test/common' ], function($, _, open
 			var c = new common(assert);
 			testDeleteWithoutTrash(c, c.createMaterial, c.findMaterial, c.deleteMaterial);
 		});
-		
-		QUnit.test("deleteVocabularyTerm()", function(assert) {
+
+		QUnit.test("deleteVocabularyTerms()", function(assert) {
 			var c = new common(assert);
 			testDeleteWithoutTrash(c, c.createVocabularyTerm, c.findVocabularyTerm, c.deleteVocabularyTerm);
-		});	
-		
-		QUnit.test("replaceVocabularyTerm()", function(assert) {
+		});
+
+		QUnit.test("replaceVocabularyTerms()", function(assert) {
 			var c = new common(assert);
 			testDeleteWithoutTrash(c, c.createVocabularyTerm, c.findVocabularyTerm, c.replaceVocabularyTerm);
 		});
 
+		QUnit.test("deleteTags()", function(assert) {
+			var c = new common(assert);
+			testDeleteWithoutTrash(c, c.createTag, c.findTag, c.deleteTag);
+		});
+
 	}
 });
diff --git a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-search.js b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-search.js
index f37d68890c5..366e962f999 100644
--- a/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-search.js
+++ b/js-test/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-search.js
@@ -389,7 +389,7 @@ define([ 'jquery', 'underscore', 'openbis', 'test/common', 'test/naturalsort' ],
 
 			testSearch(c, fSearch, fCheck);
 		});
-		
+
 		QUnit.test("searchDataSets()", function(assert) {
 			var c = new common(assert);
 
@@ -813,5 +813,24 @@ define([ 'jquery', 'underscore', 'openbis', 'test/common', 'test/naturalsort' ],
 			testSearch(c, fSearch, fCheck);
 		});
 
+		QUnit.test("searchTags()", function(assert) {
+			var c = new common(assert);
+
+			var fSearch = function(facade) {
+				var criteria = new c.TagSearchCriteria();
+				criteria.withCode().thatEquals("JS_TEST_METAPROJECT");
+				return facade.searchTags(criteria, c.createTagFetchOptions());
+			}
+
+			var fCheck = function(facade, tags) {
+				c.assertEqual(tags.length, 1);
+				var tag = tags[0];
+				c.assertEqual(tag.getCode(), "JS_TEST_METAPROJECT", "Code");
+				c.assertEqual(tag.getPermId().getPermId(), "/openbis_test_js/JS_TEST_METAPROJECT", "PermId");
+			}
+
+			testSearch(c, fSearch, fCheck);
+		});
+
 	}
 });
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 3257521f6be..d39c0b47df6 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
@@ -99,9 +99,11 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search.SpaceSearchCriteria
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.update.SpaceUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.Tag;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.create.TagCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.deletion.TagDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.fetchoptions.TagFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.TagPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.update.TagUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyTermCreation;
@@ -126,9 +128,9 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IDeleteMateri
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IDeleteProjectMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IDeleteSampleMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IDeleteSpaceMethodExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IDeleteTagMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IDeleteVocabularyTermMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IExecuteCustomASServiceMethodExecutor;
-import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGlobalSearchMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGetDataSetMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGetExperimentMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGetMaterialMethodExecutor;
@@ -137,6 +139,7 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGetSampleMet
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGetSpaceMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGetTagMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGetVocabularyTermMethodExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IGlobalSearchMethodExecutor;
 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;
@@ -151,6 +154,7 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchProjec
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchSampleMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchSampleTypeMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchSpaceMethodExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchTagMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.ISearchVocabularyTermMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IUpdateDataSetMethodExecutor;
 import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.method.IUpdateExperimentMethodExecutor;
@@ -293,6 +297,9 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
     @Autowired
     private ISearchVocabularyTermMethodExecutor searchVocabularyTermExecutor;
 
+    @Autowired
+    private ISearchTagMethodExecutor searchTagExecutor;
+
     @Autowired
     private ISearchCustomASServiceMethodExecutor searchCustomASServiceExecutor;
 
@@ -320,6 +327,9 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
     @Autowired
     private IDeleteVocabularyTermMethodExecutor deleteVocabularyTermExecutor;
 
+    @Autowired
+    private IDeleteTagMethodExecutor deleteTagExecutor;
+
     @Autowired
     private ISearchDeletionMethodExecutor searchDeletionExecutor;
 
@@ -695,6 +705,14 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
         return searchVocabularyTermExecutor.search(sessionToken, searchCriteria, fetchOptions);
     }
 
+    @Override
+    @Transactional(readOnly = true)
+    @RolesAllowed({ RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    public SearchResult<Tag> searchTags(String sessionToken, TagSearchCriteria searchCriteria, TagFetchOptions fetchOptions)
+    {
+        return searchTagExecutor.search(sessionToken, searchCriteria, fetchOptions);
+    }
+
     @Override
     @Transactional
     @DatabaseCreateOrDeleteModification(value = { ObjectKind.SPACE, ObjectKind.DELETION })
@@ -765,6 +783,16 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi>
         deleteVocabularyTermExecutor.delete(sessionToken, termIds, deletionOptions);
     }
 
+    @Override
+    @Transactional
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.METAPROJECT, ObjectKind.DELETION })
+    @RolesAllowed({ RoleWithHierarchy.SPACE_USER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    @Capability("DELETE_TAG")
+    public void deleteTags(String sessionToken, List<? extends ITagId> tagIds, TagDeletionOptions deletionOptions)
+    {
+        deleteTagExecutor.delete(sessionToken, tagIds, deletionOptions);
+    }
+
     @Override
     @Transactional(readOnly = true)
     @RolesAllowed({ RoleWithHierarchy.SPACE_USER, 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 1a25bf57f4a..1826b26776e 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
@@ -95,9 +95,11 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search.SpaceSearchCriteria
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.update.SpaceUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.Tag;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.create.TagCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.deletion.TagDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.fetchoptions.TagFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.TagPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.update.TagUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyTermCreation;
@@ -400,6 +402,13 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements
         return null;
     }
 
+    @Override
+    public SearchResult<Tag> searchTags(String sessionToken, TagSearchCriteria searchCriteria, TagFetchOptions fetchOptions)
+    {
+        logAccess(sessionToken, "search-tags", "SEARCH_CRITERIA:\n%s\nFETCH_OPTIONS:\n%s\n", searchCriteria, fetchOptions);
+        return null;
+    }
+
     @Override
     public void deleteSpaces(String sessionToken, List<? extends ISpaceId> spaceIds, SpaceDeletionOptions deletionOptions)
     {
@@ -445,6 +454,12 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements
         logAccess(sessionToken, "delete-vocabulary-terms", "VOCABULARY_TERM_IDS(%s) DELETION_OPTIONS(%s)", abbreviate(termIds), deletionOptions);
     }
 
+    @Override
+    public void deleteTags(String sessionToken, List<? extends ITagId> tagIds, TagDeletionOptions deletionOptions)
+    {
+        logAccess(sessionToken, "delete-tags", "TAG_IDS(%s) DELETION_OPTIONS(%s)", abbreviate(tagIds), deletionOptions);
+    }
+
     @Override
     public SearchResult<Deletion> searchDeletions(String sessionToken, DeletionSearchCriteria searchCriteria, DeletionFetchOptions fetchOptions)
     {
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/DeleteTagMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/DeleteTagMethodExecutor.java
new file mode 100644
index 00000000000..37c6faaaed6
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/DeleteTagMethodExecutor.java
@@ -0,0 +1,44 @@
+/*
+ * 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 org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.deletion.TagDeletionOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.entity.IDeleteEntityExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.tag.IDeleteTagExecutor;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class DeleteTagMethodExecutor extends AbstractDeleteMethodExecutor<Void, ITagId, TagDeletionOptions>
+        implements IDeleteTagMethodExecutor
+{
+
+    @Autowired
+    private IDeleteTagExecutor deleteExecutor;
+
+    @Override
+    protected IDeleteEntityExecutor<Void, ITagId, TagDeletionOptions> getDeleteExecutor()
+    {
+        return deleteExecutor;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IDeleteTagMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IDeleteTagMethodExecutor.java
new file mode 100644
index 00000000000..2dced850eee
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/IDeleteTagMethodExecutor.java
@@ -0,0 +1,28 @@
+/*
+ * 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.tag.deletion.TagDeletionOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
+
+/**
+ * @author pkupczyk
+ */
+public interface IDeleteTagMethodExecutor extends IDeleteMethodExecutor<Void, ITagId, TagDeletionOptions>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/ISearchTagMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/ISearchTagMethodExecutor.java
new file mode 100644
index 00000000000..e3038fd8fdf
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/ISearchTagMethodExecutor.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.tag.Tag;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.fetchoptions.TagFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
+
+/**
+ * @author pkupczyk
+ */
+public interface ISearchTagMethodExecutor extends ISearchMethodExecutor<Tag, TagSearchCriteria, TagFetchOptions>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/SearchTagMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/SearchTagMethodExecutor.java
new file mode 100644
index 00000000000..accf55e0bc9
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/method/SearchTagMethodExecutor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.Tag;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.fetchoptions.TagFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.tag.ISearchTagExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.ITranslator;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.translator.entity.tag.ITagTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MetaprojectPE;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class SearchTagMethodExecutor
+        extends AbstractIdSearchMethodExecutor<Tag, MetaprojectPE, TagSearchCriteria, TagFetchOptions>
+        implements ISearchTagMethodExecutor
+{
+
+    @Autowired
+    private ISearchTagExecutor searchExecutor;
+
+    @Autowired
+    private ITagTranslator translator;
+
+    @Override
+    protected List<MetaprojectPE> searchPEs(IOperationContext context, TagSearchCriteria criteria)
+    {
+        return searchExecutor.search(context, criteria);
+    }
+
+    @Override
+    protected ITranslator<Long, Tag, TagFetchOptions> getTranslator()
+    {
+        return translator;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/DeleteTagExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/DeleteTagExecutor.java
new file mode 100644
index 00000000000..5961d9701bc
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/DeleteTagExecutor.java
@@ -0,0 +1,76 @@
+/*
+ * 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.asapi.v3.executor.tag;
+
+import java.util.Collection;
+import java.util.List;
+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.tag.deletion.TagDeletionOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.entity.AbstractDeleteEntityExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.helper.tag.TagAuthorization;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.IMetaprojectBO;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.id.metaproject.MetaprojectTechIdId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MetaprojectPE;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class DeleteTagExecutor extends AbstractDeleteEntityExecutor<Void, ITagId, MetaprojectPE, TagDeletionOptions> implements
+        IDeleteTagExecutor
+{
+
+    @Autowired
+    private IMapTagByIdExecutor mapTagByIdExecutor;
+
+    @Override
+    protected Map<ITagId, MetaprojectPE> map(IOperationContext context, List<? extends ITagId> entityIds)
+    {
+        return mapTagByIdExecutor.map(context, entityIds);
+    }
+
+    @Override
+    protected void checkAccess(IOperationContext context, ITagId entityId, MetaprojectPE entity)
+    {
+        new TagAuthorization(context).checkAccess(entity);
+    }
+
+    @Override
+    protected void updateModificationDateAndModifier(IOperationContext context, MetaprojectPE entity)
+    {
+        // nothing to do
+    }
+
+    @Override
+    protected Void delete(IOperationContext context, Collection<MetaprojectPE> tags, TagDeletionOptions deletionOptions)
+    {
+        for (MetaprojectPE tag : tags)
+        {
+            IMetaprojectBO metaprojectBO = businessObjectFactory.createMetaprojectBO(context.getSession());
+            metaprojectBO.deleteByMetaprojectId(new MetaprojectTechIdId(tag.getId()), deletionOptions.getReason());
+        }
+
+        return null;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/IDeleteTagExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/IDeleteTagExecutor.java
new file mode 100644
index 00000000000..a9bf408a4e8
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/IDeleteTagExecutor.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.tag;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.deletion.TagDeletionOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.entity.IDeleteEntityExecutor;
+
+/**
+ * @author pkupczyk
+ */
+public interface IDeleteTagExecutor extends IDeleteEntityExecutor<Void, ITagId, TagDeletionOptions>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/ISearchTagExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/ISearchTagExecutor.java
new file mode 100644
index 00000000000..e4961fa1fe3
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/ISearchTagExecutor.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.asapi.v3.executor.tag;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.ISearchObjectExecutor;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MetaprojectPE;
+
+/**
+ * @author pkupczyk
+ */
+public interface ISearchTagExecutor extends ISearchObjectExecutor<TagSearchCriteria, MetaprojectPE>
+{
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/MapTagByIdExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/MapTagByIdExecutor.java
index 60cf42772d0..b37f33d285a 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/MapTagByIdExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/MapTagByIdExecutor.java
@@ -62,13 +62,16 @@ public class MapTagByIdExecutor implements IMapTagByIdExecutor
         {
             for (ITagId tagId : tagIds)
             {
-                MetaprojectIdentifier identifier = getTagIdentifierExecutor.getIdentifier(context, tagId);
-                MetaprojectPE tag =
-                        daoFactory.getMetaprojectDAO()
-                                .tryFindByOwnerAndName(identifier.getMetaprojectOwnerId(), identifier.getMetaprojectName());
-                if (tag != null)
+                if (tagId != null)
                 {
-                    map.put(tagId, tag);
+                    MetaprojectIdentifier identifier = getTagIdentifierExecutor.getIdentifier(context, tagId);
+                    MetaprojectPE tag =
+                            daoFactory.getMetaprojectDAO()
+                                    .tryFindByOwnerAndName(identifier.getMetaprojectOwnerId(), identifier.getMetaprojectName());
+                    if (tag != null)
+                    {
+                        map.put(tagId, tag);
+                    }
                 }
             }
         }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/SearchTagExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/SearchTagExecutor.java
new file mode 100644
index 00000000000..0900eeb317b
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/tag/SearchTagExecutor.java
@@ -0,0 +1,102 @@
+/*
+ * 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.asapi.v3.executor.tag;
+
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.CodeSearchCriteria;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.ISearchCriteria;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.IdSearchCriteria;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.PermIdSearchCriteria;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.TagPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.AbstractSearchObjectManuallyExecutor;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.CodeMatcher;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.Matcher;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.SimpleFieldMatcher;
+import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.StringFieldMatcher;
+import ch.systemsx.cisd.openbis.generic.shared.dto.MetaprojectPE;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+public class SearchTagExecutor extends AbstractSearchObjectManuallyExecutor<TagSearchCriteria, MetaprojectPE> implements
+        ISearchTagExecutor
+{
+
+    @Override
+    protected List<MetaprojectPE> listAll()
+    {
+        return daoFactory.getMetaprojectDAO().listAllEntities();
+    }
+
+    @Override
+    protected Matcher<MetaprojectPE> getMatcher(ISearchCriteria criteria)
+    {
+        if (criteria instanceof IdSearchCriteria<?>)
+        {
+            return new IdMatcher();
+        } else if (criteria instanceof PermIdSearchCriteria)
+        {
+            return new PermIdMatcher();
+        } else if (criteria instanceof CodeSearchCriteria)
+        {
+            return new CodeMatcher<MetaprojectPE>();
+        } else
+        {
+            throw new IllegalArgumentException("Unknown search criteria: " + criteria.getClass());
+        }
+    }
+
+    private class IdMatcher extends SimpleFieldMatcher<MetaprojectPE>
+    {
+
+        @Override
+        protected boolean isMatching(IOperationContext context, MetaprojectPE object, ISearchCriteria criteria)
+        {
+            Object id = ((IdSearchCriteria<?>) criteria).getId();
+
+            if (id == null)
+            {
+                return true;
+            } else if (id instanceof TagPermId)
+            {
+                return id.equals(new TagPermId(object.getOwner().getUserId(), object.getCode()));
+            } else
+            {
+                throw new IllegalArgumentException("Unknown id: " + id.getClass());
+            }
+        }
+
+    }
+
+    private class PermIdMatcher extends StringFieldMatcher<MetaprojectPE>
+    {
+
+        @Override
+        protected String getFieldValue(MetaprojectPE object)
+        {
+            return new TagPermId(object.getOwner().getUserId(), object.getCode()).toString();
+        }
+
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/common/search/AbstractCompositeSearchCriteria.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/common/search/AbstractCompositeSearchCriteria.js
index 0a4e7d033ff..f1f03609488 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/common/search/AbstractCompositeSearchCriteria.js
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/common/search/AbstractCompositeSearchCriteria.js
@@ -25,9 +25,11 @@ define([ "stjs", "as/dto/common/search/AbstractSearchCriteria", "as/dto/common/s
 		}
 		prototype.withOrOperator = function() {
 			this.operator = SearchOperator.OR;
+			return this;
 		}
 		prototype.withAndOperator = function() {
 			this.operator = SearchOperator.AND;
+			return this;
 		}
 	}, {
 		criteria : {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/tag/delete/TagDeletionOptions.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/tag/delete/TagDeletionOptions.js
new file mode 100644
index 00000000000..64b3006d257
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/as/dto/tag/delete/TagDeletionOptions.js
@@ -0,0 +1,13 @@
+/**
+ * @author pkupczyk
+ */
+define([ "stjs", "as/dto/deletion/AbstractObjectDeletionOptions" ], function(stjs, AbstractObjectDeletionOptions) {
+	var TagDeletionOptions = function() {
+		AbstractObjectDeletionOptions.call(this);
+	};
+	stjs.extend(TagDeletionOptions, AbstractObjectDeletionOptions, [ AbstractObjectDeletionOptions ], function(constructor, prototype) {
+		prototype['@type'] = 'as.dto.tag.delete.TagDeletionOptions';
+		constructor.serialVersionUID = 1;
+	}, {});
+	return TagDeletionOptions;
+})
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
index c6f41e81632..535dc9f791f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/resources/api/v3/openbis.js
@@ -1,6 +1,6 @@
 define([ 'jquery', 'util/Json' ], function(jquery, stjsUtil) {
 	jquery.noConflict();
-	
+
 	var __private = function() {
 
 		this.ajaxRequest = function(settings) {
@@ -590,6 +590,18 @@ define([ 'jquery', 'util/Json' ], function(jquery, stjsUtil) {
 			});
 		}
 
+		this.searchTags = function(criteria, fetchOptions) {
+			var thisFacade = this;
+			return thisFacade._private.ajaxRequest({
+				url : openbisUrl,
+				data : {
+					"method" : "searchTags",
+					"params" : [ thisFacade._private.sessionToken, criteria, fetchOptions ]
+				},
+				returnType : "SearchResult"
+			});
+		}
+
 		this.searchCustomASServices = function(criteria, fetchOptions) {
 			var thisFacade = this;
 			return thisFacade._private.ajaxRequest({
@@ -706,6 +718,17 @@ define([ 'jquery', 'util/Json' ], function(jquery, stjsUtil) {
 			});
 		}
 
+		this.deleteTags = function(ids, deletionOptions) {
+			var thisFacade = this;
+			return thisFacade._private.ajaxRequest({
+				url : openbisUrl,
+				data : {
+					"method" : "deleteTags",
+					"params" : [ thisFacade._private.sessionToken, ids, deletionOptions ]
+				}
+			});
+		}
+
 		this.searchDeletions = function(criteria, fetchOptions) {
 			var thisFacade = this;
 			return thisFacade._private.ajaxRequest({
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/DeleteTagTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/DeleteTagTest.java
new file mode 100644
index 00000000000..e432665d202
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/DeleteTagTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.systemtest.asapi.v3;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.DataSetPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.ExperimentIdentifier;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.id.MaterialPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.Tag;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.create.TagCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.deletion.TagDeletionOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.fetchoptions.TagFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.TagPermId;
+import ch.systemsx.cisd.common.action.IDelegatedAction;
+
+/**
+ * @author pkupczyk
+ */
+public class DeleteTagTest extends AbstractDeletionTest
+{
+
+    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Entity ids cannot be null.*")
+    public void testDeleteWithNullTagIds()
+    {
+        TagDeletionOptions options = new TagDeletionOptions();
+        options.setReason("It is just a test");
+
+        deleteTag(TEST_USER, PASSWORD, null, options);
+    }
+
+    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Deletion options cannot be null.*")
+    public void testDeleteWithNullOptions()
+    {
+        TagCreation creation = new TagCreation();
+        creation.setCode("TAG_TO_DELETE");
+
+        Tag before = createTag(TEST_USER, PASSWORD, creation);
+
+        deleteTag(TEST_USER, PASSWORD, before.getPermId(), null);
+    }
+
+    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Deletion reason cannot be null.*")
+    public void testDeleteWithNullReason()
+    {
+        TagCreation creation = new TagCreation();
+        creation.setCode("TAG_TO_DELETE");
+
+        Tag before = createTag(TEST_USER, PASSWORD, creation);
+
+        TagDeletionOptions options = new TagDeletionOptions();
+
+        deleteTag(TEST_USER, PASSWORD, before.getPermId(), options);
+    }
+
+    @Test
+    public void testDeleteWithEmptyTag()
+    {
+        TagCreation creation = new TagCreation();
+        creation.setCode("TAG_TO_DELETE");
+
+        Tag before = createTag(TEST_USER, PASSWORD, creation);
+
+        TagDeletionOptions options = new TagDeletionOptions();
+        options.setReason("It is just a test");
+
+        Tag after = deleteTag(TEST_USER, PASSWORD, before.getPermId(), options);
+        assertNull(after);
+    }
+
+    @Test
+    public void testDeleteWithNotEmptyTag()
+    {
+        TagCreation creation = new TagCreation();
+        creation.setCode("TAG_TO_DELETE");
+        creation.setExperimentIds(Arrays.asList(new ExperimentIdentifier("/CISD/NEMO/EXP10")));
+        creation.setSampleIds(Arrays.asList(new SampleIdentifier("/CISD/CP-TEST-1")));
+        creation.setDataSetIds(Arrays.asList(new DataSetPermId("20120619092259000-22")));
+        creation.setMaterialIds(Arrays.asList(new MaterialPermId("AD3", "VIRUS")));
+
+        Tag before = createTag(TEST_USER, PASSWORD, creation);
+
+        TagDeletionOptions options = new TagDeletionOptions();
+        options.setReason("It is just a test");
+
+        Tag after = deleteTag(TEST_USER, PASSWORD, before.getPermId(), options);
+        assertNull(after);
+    }
+
+    @Test
+    public void testDeleteWithUnauthorizedTag()
+    {
+        TagCreation creation = new TagCreation();
+        creation.setCode("TAG_TO_DELETE");
+
+        final Tag before = createTag(TEST_SPACE_USER, PASSWORD, creation);
+
+        final TagDeletionOptions options = new TagDeletionOptions();
+        options.setReason("It is just a test");
+
+        assertUnauthorizedObjectAccessException(new IDelegatedAction()
+            {
+                @Override
+                public void execute()
+                {
+                    deleteTag(TEST_USER, PASSWORD, before.getPermId(), options);
+                }
+            }, before.getPermId());
+    }
+
+    private Tag createTag(String user, String password, TagCreation creation)
+    {
+        String sessionToken = v3api.login(user, password);
+
+        List<TagPermId> permIds = v3api.createTags(sessionToken, Arrays.asList(creation));
+
+        Map<ITagId, Tag> map = v3api.getTags(sessionToken, permIds, new TagFetchOptions());
+        assertEquals(map.size(), 1);
+
+        return map.get(permIds.get(0));
+    }
+
+    private Tag deleteTag(String user, String password, ITagId id, TagDeletionOptions options)
+    {
+        String sessionToken = v3api.login(user, password);
+
+        List<ITagId> ids = null;
+
+        if (id != null)
+        {
+            ids = Arrays.asList(id);
+        }
+
+        v3api.deleteTags(sessionToken, ids, options);
+
+        Map<ITagId, Tag> map = v3api.getTags(sessionToken, ids, new TagFetchOptions());
+
+        return map.get(id);
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchTagTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchTagTest.java
new file mode 100644
index 00000000000..69af0172339
--- /dev/null
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/SearchTagTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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 java.util.List;
+
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.Tag;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.fetchoptions.TagFetchOptions;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.TagPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
+
+/**
+ * @author pkupczyk
+ */
+public class SearchTagTest extends AbstractTest
+{
+
+    @Test
+    public void testSearchWithEmptyCriteria()
+    {
+        testSearch(TEST_USER, new TagSearchCriteria(), "/test/TEST_METAPROJECTS", "/test/ANOTHER_TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithIdSetToPermId()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withId().thatEquals(new TagPermId(TEST_USER, "TEST_METAPROJECTS"));
+        testSearch(TEST_USER, criteria, "/test/TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithIdSetToNonexistentPermId()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withId().thatEquals(new TagPermId("/test/IDONTEXIST"));
+        testSearch(TEST_USER, criteria);
+    }
+
+    @Test
+    public void testSearchWithPermIdThatEquals()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withPermId().thatEquals("/test/TEST_METAPROJECTS");
+        testSearch(TEST_USER, criteria, "/test/TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithPermIdThatContains()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withPermId().thatContains("est/ANOT");
+        testSearch(TEST_USER, criteria, "/test/ANOTHER_TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithPermIdThatStartsWith()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withPermId().thatStartsWith("/test/TEST");
+        testSearch(TEST_USER, criteria, "/test/TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithPermIdThatEndsWith()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withPermId().thatEndsWith("role/TEST_METAPROJECTS_2");
+        testSearch(TEST_POWER_USER_CISD, criteria, "/test_role/TEST_METAPROJECTS_2");
+    }
+
+    @Test
+    public void testSearchWithCodeThatEquals()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withCode().thatEquals("TEST_METAPROJECTS");
+        testSearch(TEST_USER, criteria, "/test/TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithCodeThatContains()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withCode().thatContains("NOTHER");
+        testSearch(TEST_USER, criteria, "/test/ANOTHER_TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithCodeThatStartsWith()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withCode().thatStartsWith("TEST");
+        testSearch(TEST_USER, criteria, "/test/TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithCodeThatEndsWith()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withCode().thatEndsWith("_2");
+        testSearch(TEST_POWER_USER_CISD, criteria, "/test_role/TEST_METAPROJECTS_2");
+    }
+
+    @Test
+    public void testSearchWithAndOperator()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withAndOperator();
+        criteria.withCode().thatContains("TEST");
+        criteria.withCode().thatContains("OTHER");
+        testSearch(TEST_USER, criteria, "/test/ANOTHER_TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithOrOperator()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withOrOperator();
+        criteria.withPermId().thatContains("TEST");
+        criteria.withPermId().thatContains("OTHER");
+        testSearch(TEST_USER, criteria, "/test/TEST_METAPROJECTS", "/test/ANOTHER_TEST_METAPROJECTS");
+    }
+
+    @Test
+    public void testSearchWithUnauthorized()
+    {
+        TagSearchCriteria criteria = new TagSearchCriteria();
+        criteria.withPermId().thatEquals("/test/TEST_METAPROJECTS");
+        testSearch(TEST_USER, criteria, "/test/TEST_METAPROJECTS");
+        testSearch(TEST_SPACE_USER, criteria);
+    }
+
+    private void testSearch(String user, TagSearchCriteria criteria, String... expectedPermIds)
+    {
+        String sessionToken = v3api.login(user, PASSWORD);
+
+        SearchResult<Tag> searchResult =
+                v3api.searchTags(sessionToken, criteria, new TagFetchOptions());
+        List<Tag> tags = searchResult.getObjects();
+
+        assertTags(tags, expectedPermIds);
+        v3api.logout(sessionToken);
+    }
+
+}
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 460b0f50735..0568e549464 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
@@ -94,9 +94,11 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search.SpaceSearchCriteria
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.update.SpaceUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.Tag;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.create.TagCreation;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.deletion.TagDeletionOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.fetchoptions.TagFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.TagPermId;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.search.TagSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.update.TagUpdate;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyTermCreation;
@@ -213,6 +215,8 @@ public interface IApplicationServerApi extends IRpcService
     public SearchResult<VocabularyTerm> searchVocabularyTerms(String sessionToken, VocabularyTermSearchCriteria searchCriteria,
             VocabularyTermFetchOptions fetchOptions);
 
+    public SearchResult<Tag> searchTags(String sessionToken, TagSearchCriteria searchCriteria, TagFetchOptions fetchOptions);
+
     public SearchResult<CustomASService> searchCustomASServices(String sessionToken, CustomASServiceSearchCriteria searchCriteria,
             CustomASServiceFetchOptions fetchOptions);
 
@@ -236,6 +240,8 @@ public interface IApplicationServerApi extends IRpcService
 
     public void deleteVocabularyTerms(String sessionToken, List<? extends IVocabularyTermId> termIds, VocabularyTermDeletionOptions deletionOptions);
 
+    public void deleteTags(String sessionToken, List<? extends ITagId> tagIds, TagDeletionOptions deletionOptions);
+
     public SearchResult<Deletion> searchDeletions(String sessionToken, DeletionSearchCriteria searchCriteria, DeletionFetchOptions fetchOptions);
 
     public void revertDeletions(String sessionToken, List<? extends IDeletionId> deletionIds);
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/deletion/TagDeletionOptions.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/deletion/TagDeletionOptions.java
new file mode 100644
index 00000000000..7e2b45b6020
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/deletion/TagDeletionOptions.java
@@ -0,0 +1,31 @@
+/*
+ * 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.asapi.v3.dto.tag.deletion;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.deletion.AbstractObjectDeletionOptions;
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author pkupczyk
+ */
+@JsonObject("as.dto.tag.delete.TagDeletionOptions")
+public class TagDeletionOptions extends AbstractObjectDeletionOptions
+{
+
+    private static final long serialVersionUID = 1L;
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/search/TagSearchCriteria.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/search/TagSearchCriteria.java
index 71000483564..c74b434ecd3 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/search/TagSearchCriteria.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/tag/search/TagSearchCriteria.java
@@ -20,6 +20,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.AbstractObjectSear
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.CodeSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.PermIdSearchCriteria;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchCriteriaToStringBuilder;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchOperator;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.tag.id.ITagId;
 import ch.systemsx.cisd.base.annotation.JsonObject;
 
@@ -46,6 +47,16 @@ public class TagSearchCriteria extends AbstractObjectSearchCriteria<ITagId>
         return with(new PermIdSearchCriteria());
     }
 
+    public TagSearchCriteria withOrOperator()
+    {
+        return (TagSearchCriteria) withOperator(SearchOperator.OR);
+    }
+
+    public TagSearchCriteria withAndOperator()
+    {
+        return (TagSearchCriteria) withOperator(SearchOperator.AND);
+    }
+
     @Override
     protected SearchCriteriaToStringBuilder createBuilder()
     {
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 5e959982697..70f68c9ccad 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
@@ -1130,4 +1130,7 @@ Tag Creation
 set DataSet Ids
 set Experiment Ids
 set Material Ids
-set Sample Ids
\ No newline at end of file
+set Sample Ids
+delete Tags
+search Tags
+Tag Deletion Options
\ No newline at end of file
-- 
GitLab