RANGER-4749: added TagREST APIs to retrieve by resource and get paginated resources along with associated tags

Signed-off-by: Madhan Neethiraj <madhan@apache.org>
diff --git a/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java b/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java
old mode 100644
new mode 100755
index 716a1a9..8113e42
--- a/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java
+++ b/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java
@@ -26,6 +26,7 @@
 import org.apache.ranger.plugin.model.AuditFilter;
 import org.apache.ranger.plugin.model.RangerGds.RangerTagDataMaskInfo;
 import org.apache.ranger.plugin.model.RangerPrincipal;
+import org.apache.ranger.plugin.model.RangerTag;
 import org.apache.ranger.plugin.model.RangerValidityRecurrence;
 import org.apache.ranger.plugin.model.RangerValiditySchedule;
 import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemDataMaskInfo;
@@ -51,6 +52,7 @@
     private static final Type TYPE_LIST_RANGER_TAG_MASK_INFO       = new TypeToken<List<RangerTagDataMaskInfo>>() {}.getType();
     private static final Type TYPE_MAP_RANGER_MASK_INFO            = new TypeToken<Map<String, RangerPolicyItemDataMaskInfo>>() {}.getType();
     private static final Type TYPE_MAP_RANGER_POLICY_RESOURCE      = new TypeToken<Map<String, RangerPolicyResource>>() {}.getType();
+    private static final Type TYPE_LIST_RANGER_TAG                 = new TypeToken<List<RangerTag>>() {}.getType();
 
     private static final ThreadLocal<Gson> gson = new ThreadLocal<Gson>() {
         @Override
@@ -189,6 +191,15 @@
         }
     }
 
+    public static List<RangerTag> jsonToRangerTagList(String jsonStr) {
+        try {
+            return gson.get().fromJson(jsonStr, TYPE_LIST_RANGER_TAG);
+        } catch (Exception e) {
+            LOG.error("Cannot get List<RangerTag> from " + jsonStr, e);
+            return null;
+        }
+    }
+
     public static Map<String, RangerPolicyItemDataMaskInfo> jsonToMapMaskInfo(String jsonStr) {
         try {
             return gson.get().fromJson(jsonStr, TYPE_MAP_RANGER_MASK_INFO);
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceResourceWithTags.java b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceResourceWithTags.java
new file mode 100755
index 0000000..f3c24d6
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceResourceWithTags.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ranger.plugin.model;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonAutoDetect;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY)
+@JsonSerialize(include=JsonSerialize.Inclusion.NON_EMPTY)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class RangerServiceResourceWithTags extends RangerServiceResource implements java.io.Serializable {
+	private static final long serialVersionUID = 1L;
+
+	private List<RangerTag>	associatedTags;
+
+	public List<RangerTag> getAssociatedTags() {
+		return associatedTags;
+	}
+
+	public void setAssociatedTags(List<RangerTag> associatedTags) {
+		this.associatedTags = associatedTags;
+	}
+
+	@Override
+	public StringBuilder toString(StringBuilder sb) {
+		sb.append("RangerServiceResourceWithTags={ ");
+
+		super.toString(sb);
+
+		sb.append("associatedTags=[");
+		if (associatedTags != null) {
+			String prefix = "";
+
+			for (RangerTag associatedTag : associatedTags) {
+	            sb.append(prefix);
+
+				associatedTag.toString(sb);
+
+				prefix = ", ";
+	        }
+		}
+		sb.append("] ");
+
+		sb.append(" }");
+
+		return sb;
+	}
+}
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/SearchFilter.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/SearchFilter.java
index b0fad0a..0da5f2a 100755
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/SearchFilter.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/SearchFilter.java
@@ -76,6 +76,7 @@
 
 	public static final String TAG_DEF_ID                = "tagDefId";            // search
 	public static final String TAG_DEF_GUID              = "tagDefGuid";          // search
+	public static final String TAG_NAMES                 = "tagNames";            // search
 	public static final String TAG_TYPE                  = "tagType";             // search
 	public static final String TAG_TYPE_PARTIAL          = "tagTypePartial";      // search
 	public static final String TAG_SOURCE                = "tagSource";           // search
@@ -88,6 +89,7 @@
 	public static final String TAG_RESOURCE_GUID         = "resourceGuid";        // search
 	public static final String TAG_RESOURCE_SERVICE_NAME = "resourceServiceName"; // search
 	public static final String TAG_RESOURCE_SIGNATURE    = "resourceSignature";   // search
+	public static final String TAG_RESOURCE_ELEMENTS     = "resourceElements";   // search
 	public static final String TAG_MAP_ID                = "tagResourceMapId";    // search
 	public static final String TAG_MAP_GUID              = "tagResourceMapGuid";  // search
 
diff --git a/security-admin/src/main/java/org/apache/ranger/biz/TagDBStore.java b/security-admin/src/main/java/org/apache/ranger/biz/TagDBStore.java
old mode 100644
new mode 100755
index a472fe1..ce59505
--- a/security-admin/src/main/java/org/apache/ranger/biz/TagDBStore.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/TagDBStore.java
@@ -21,6 +21,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -45,6 +46,7 @@
 import org.apache.ranger.entity.XXTagDef;
 import org.apache.ranger.entity.XXTagResourceMap;
 import org.apache.ranger.plugin.model.*;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
 import org.apache.ranger.plugin.model.validation.RangerValidityScheduleValidator;
 import org.apache.ranger.plugin.model.validation.ValidationFailureDetails;
 import org.apache.ranger.plugin.store.AbstractTagStore;
@@ -59,7 +61,9 @@
 import org.apache.ranger.service.RangerTagDefService;
 import org.apache.ranger.service.RangerTagResourceMapService;
 import org.apache.ranger.service.RangerTagService;
+import org.apache.ranger.view.RangerServiceResourceWithTagsList;
 import org.apache.ranger.service.RangerServiceResourceService;
+import org.apache.ranger.service.RangerServiceResourceWithTagsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -90,6 +94,9 @@
 	RangerServiceResourceService rangerServiceResourceService;
 
 	@Autowired
+	RangerServiceResourceWithTagsService rangerServiceResourceWithTagsService;
+
+	@Autowired
 	RangerTagResourceMapService rangerTagResourceMapService;
 
 	@Autowired
@@ -714,6 +721,10 @@
 		return ret;
 	}
 
+	public RangerServiceResourceWithTagsList getPaginatedServiceResourcesWithTags(SearchFilter filter) throws Exception {
+		return rangerServiceResourceWithTagsService.searchServiceResourcesWithTags(filter);
+	}
+
 
 	@Override
 	public RangerTagResourceMap createTagResourceMap(RangerTagResourceMap tagResourceMap) throws Exception {
@@ -1386,4 +1397,56 @@
 			}
 		}
 	}
+
+	public RangerServiceResource getRangerServiceResource(String serviceName, Map<String, String[]> resourceMap) {
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("==> TagDBStore.getRangerServiceResource(): serviceName={" + serviceName + "}");
+		}
+
+		Map<String, RangerPolicyResource> resourceElements = new HashMap<>();
+
+		for (Map.Entry<String, String[]> entry : resourceMap.entrySet()) {
+			String[] parts      = entry.getKey().split("\\.");
+			String[] valueArray = entry.getValue();
+
+			if (parts.length < 1 || valueArray == null) {
+				continue;
+			}
+
+			String key = parts[0];
+
+			RangerPolicyResource policyResource = resourceElements.get(key);
+
+			if (policyResource == null) {
+				policyResource = new RangerPolicyResource();
+
+				resourceElements.put(key, policyResource);
+			}
+
+			if (parts.length == 1) {
+				List<String> valueList = new ArrayList<>();
+
+				for (String str : valueArray) {
+					valueList.add(str.trim());
+				}
+			} else if (parts.length == 2 && valueArray[0] != null) {
+				String subKey = parts[1];
+				String value  = valueArray[0];
+
+				if (subKey.equalsIgnoreCase("isExcludes")) {
+					policyResource.setIsExcludes(Boolean.parseBoolean(value.trim()));
+				} else if (subKey.equalsIgnoreCase("isRecursive")) {
+					policyResource.setIsRecursive(Boolean.parseBoolean(value.trim()));
+				}
+			}
+		}
+
+		RangerServiceResource ret = new RangerServiceResource(serviceName, resourceElements);
+
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("<== TagDBStore.getRangerServiceResource(): (serviceName={" + serviceName + "} RangerServiceResource={" + ret + "})");
+		}
+
+		return ret;
+	}
 }
diff --git a/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java b/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java
index c816ad2..fcef332 100755
--- a/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java
+++ b/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java
@@ -19,11 +19,7 @@
 
  package org.apache.ranger.common;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import javax.annotation.Nonnull;
 import javax.persistence.EntityManager;
@@ -106,6 +102,7 @@
 		ret.setParam(SearchFilter.TAG_SERVICE_NAME_PARTIAL, request.getParameter(SearchFilter.TAG_SERVICE_NAME_PARTIAL));
 		ret.setParam(SearchFilter.TAG_RESOURCE_GUID, request.getParameter(SearchFilter.TAG_RESOURCE_GUID));
 		ret.setParam(SearchFilter.TAG_RESOURCE_SIGNATURE, request.getParameter(SearchFilter.TAG_RESOURCE_SIGNATURE));
+		ret.setParam(SearchFilter.TAG_RESOURCE_ELEMENTS, request.getParameter(SearchFilter.TAG_RESOURCE_ELEMENTS));
 		ret.setParam(SearchFilter.TAG_DEF_GUID, request.getParameter(SearchFilter.TAG_DEF_GUID));
 		ret.setParam(SearchFilter.TAG_DEF_ID, request.getParameter(SearchFilter.TAG_DEF_ID));
 		ret.setParam(SearchFilter.TAG_ID, request.getParameter(SearchFilter.TAG_ID));
@@ -368,6 +365,45 @@
 						whereClause.append(" and ").append(searchField.getCustomCondition());
 					}
 				}
+			} else if (isMultiValue && searchField.getDataType() == SearchField.DATA_TYPE.STR_LIST) {
+				List<String> strValueList = new ArrayList<>();
+
+				for (Object value : multiValue) {
+					strValueList.add(String.valueOf(value));
+				}
+
+				if (!strValueList.isEmpty()) {
+					if (searchField.getCustomCondition() == null) {
+						if (strValueList.size() <= minInListLength) {
+							whereClause.append(" and ");
+
+							if (strValueList.size() > 1) {
+								whereClause.append(" ( ");
+							}
+
+							for (int count = 0; count < strValueList.size(); count++) {
+								if (count > 0) {
+									whereClause.append(" or ");
+								}
+
+								whereClause.append(searchField.getFieldName()).append("= :")
+								           .append(searchField.getClientFieldName()).append("_").append(count);
+							}
+
+							if (strValueList.size() > 1) {
+								whereClause.append(" ) ");
+							}
+
+						} else {
+							whereClause.append(" and ")
+							           .append(searchField.getFieldName())
+							           .append(" in ")
+							           .append(" (:").append(searchField.getClientFieldName()).append(")");
+						}
+					} else {
+						whereClause.append(" and ").append(searchField.getCustomCondition());
+					}
+				}
 			} else if (searchField.getDataType() == SearchField.DATA_TYPE.INTEGER) {
 				Integer paramVal = restErrorUtil.parseInt(searchCriteria.getParam(searchField.getClientFieldName()),
 						"Invalid value for " + searchField.getClientFieldName(),
@@ -477,6 +513,22 @@
 						query.setParameter(searchField.getClientFieldName(), intValueList);
 					}
 				}
+			} else if (isMultiValue && searchField.getDataType() == SearchField.DATA_TYPE.STR_LIST) {
+				List<String> strValueList = new ArrayList<>();
+
+				for (Object value : multiValue) {
+					strValueList.add(String.valueOf(value));
+				}
+
+				if (!strValueList.isEmpty()) {
+					if (strValueList.size() <= minInListLength) {
+						for (int idx = 0; idx < strValueList.size(); idx++) {
+							query.setParameter(searchField.getClientFieldName() + "_" + idx, strValueList.get(idx));
+						}
+					} else {
+						query.setParameter(searchField.getClientFieldName(), strValueList);
+					}
+				}
 			} else if (searchField.getDataType() == SearchField.DATA_TYPE.INTEGER) {
 				Integer paramVal = restErrorUtil.parseInt(searchCriteria.getParam(searchField.getClientFieldName()),
 						"Invalid value for " + searchField.getClientFieldName(),
@@ -599,6 +651,42 @@
 		}
 	}
 
+	public void extractStringList(HttpServletRequest request, SearchFilter searchFilter, String paramName,
+			                      String userFriendlyParamName, String listName, String[] validValues, String regEx) {
+		String[] values = getParamMultiValues(request, paramName);
+
+		if (values != null) {
+			List<String> stringList = new ArrayList<>(values.length);
+
+			for (String value : values) {
+				if (!stringUtil.isEmpty(regEx)) {
+					restErrorUtil.validateString(value, regEx, "Invalid value for " + userFriendlyParamName, MessageEnums.INVALID_INPUT_DATA, null, paramName);
+				}
+
+				stringList.add(value);
+			}
+
+			searchFilter.setMultiValueParam(paramName, stringList.toArray());
+		}
+	}
+
+	public Map<String, String[]> getMultiValueParamsWithPrefix(HttpServletRequest request, String prefix, boolean stripPrefix) {
+		Map<String, String[]> ret = new HashMap<String, String[]>();
+		for (Map.Entry<String, String[]> e : request.getParameterMap().entrySet()) {
+			String name = e.getKey();
+			String[] values = e.getValue();
+
+			if (!StringUtils.isEmpty(name) && !ArrayUtils.isEmpty(values)
+					&& name.startsWith(prefix)) {
+	            if(stripPrefix) {
+					name = name.substring(prefix.length());
+				}
+	            ret.put(name, values);
+			}
+		}
+        return ret;
+	}
+
 	/**
 	 * @param request
 	 * @param paramName
diff --git a/security-admin/src/main/java/org/apache/ranger/rest/TagREST.java b/security-admin/src/main/java/org/apache/ranger/rest/TagREST.java
index 09d7715..882bf4d 100755
--- a/security-admin/src/main/java/org/apache/ranger/rest/TagREST.java
+++ b/security-admin/src/main/java/org/apache/ranger/rest/TagREST.java
@@ -40,6 +40,7 @@
 import org.apache.ranger.plugin.model.RangerTagDef;
 import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
 import org.apache.ranger.plugin.store.PList;
+import org.apache.ranger.plugin.store.RangerServiceResourceSignature;
 import org.apache.ranger.plugin.store.TagStore;
 import org.apache.ranger.plugin.store.TagValidator;
 import org.apache.ranger.plugin.util.RangerPerfTracer;
@@ -47,9 +48,11 @@
 import org.apache.ranger.plugin.util.SearchFilter;
 import org.apache.ranger.plugin.util.ServiceTags;
 import org.apache.ranger.service.RangerServiceResourceService;
+import org.apache.ranger.service.RangerServiceResourceWithTagsService;
 import org.apache.ranger.service.RangerTagDefService;
 import org.apache.ranger.service.RangerTagResourceMapService;
 import org.apache.ranger.service.RangerTagService;
+import org.apache.ranger.view.RangerServiceResourceWithTagsList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -75,6 +78,7 @@
 import javax.ws.rs.core.Context;
 
 import java.util.List;
+import java.util.Map;
 
 @Path(TagRESTConstants.TAGDEF_NAME_AND_VERSION)
 @Component
@@ -119,6 +123,9 @@
     RangerServiceResourceService rangerServiceResourceService;
 
     @Autowired
+    RangerServiceResourceWithTagsService rangerServiceResourceWithTagsService;
+
+    @Autowired
     RangerTagResourceMapService rangerTagResourceMapService;
 
     public TagREST() {
@@ -1012,6 +1019,27 @@
     }
 
     @GET
+    @Path(TagRESTConstants.RESOURCE_RESOURCE + "service/{serviceName}/resource")
+    @Produces({ "application/json" })
+    @PreAuthorize("hasRole('ROLE_SYS_ADMIN')")
+    public RangerServiceResource getServiceResourceByResource(@PathParam("serviceName") String serviceName, @Context HttpServletRequest request) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> TagREST.getServiceResourceByResource(" + serviceName + ")");
+        }
+
+        Map<String, String[]> resourceMap     = searchUtil.getMultiValueParamsWithPrefix(request, SearchFilter.RESOURCE_PREFIX, true);
+        RangerServiceResource serviceResource = tagStore.getRangerServiceResource(serviceName, resourceMap);
+
+        serviceResource = getServiceResourceByServiceAndResourceSignature(serviceName, new RangerServiceResourceSignature(serviceResource).getSignature());
+
+        if(LOG.isDebugEnabled()) {
+            LOG.debug("<== TagREST.getServiceResourceByResource(serviceName={" + serviceName + "} RangerServiceResource={" + serviceResource + "})");
+        }
+
+        return serviceResource;
+    }
+
+    @GET
     @Path(TagRESTConstants.RESOURCES_RESOURCE)
     @Produces({ "application/json" })
     @PreAuthorize("hasRole('ROLE_SYS_ADMIN')")
@@ -1041,18 +1069,18 @@
     @Path(TagRESTConstants.RESOURCES_RESOURCE_PAGINATED)
     @Produces({ "application/json" })
     @PreAuthorize("hasRole('ROLE_SYS_ADMIN')")
-    public PList<RangerServiceResource> getServiceResources(@Context HttpServletRequest request) {
+    public RangerServiceResourceWithTagsList getServiceResourcesWithTags(@Context HttpServletRequest request) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("==> TagREST.getServiceResources()");
         }
 
-        final PList<RangerServiceResource> ret;
+        RangerServiceResourceWithTagsList ret;
 
         try {
-            SearchFilter filter = searchUtil.getSearchFilter(request, rangerServiceResourceService.sortFields);
+            SearchFilter filter = searchUtil.getSearchFilter(request, rangerServiceResourceWithTagsService.sortFields);
             searchUtil.extractIntList(request, filter, SearchFilter.TAG_RESOURCE_IDS, "Tag resource list");
-
-            ret = tagStore.getPaginatedServiceResources(filter);
+            searchUtil.extractStringList(request, filter, SearchFilter.TAG_NAMES, "Tag type List", "tagTypes", null, null);
+            ret = tagStore.getPaginatedServiceResourcesWithTags(filter);
         } catch (Exception excp) {
             LOG.error("getServiceResources() failed", excp);
 
diff --git a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceResourceWithTagsService.java b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceResourceWithTagsService.java
new file mode 100755
index 0000000..2b3acd1
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceResourceWithTagsService.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ranger.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.ranger.biz.RangerTagDBRetriever;
+import org.apache.ranger.common.SearchField;
+import org.apache.ranger.common.SortField;
+import org.apache.ranger.common.SearchField.DATA_TYPE;
+import org.apache.ranger.common.SearchField.SEARCH_TYPE;
+import org.apache.ranger.entity.XXServiceResource;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
+import org.apache.ranger.plugin.model.RangerServiceResourceWithTags;
+import org.apache.ranger.plugin.util.SearchFilter;
+import org.apache.ranger.view.RangerServiceResourceWithTagsList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class RangerServiceResourceWithTagsService extends RangerServiceResourceWithTagsServiceBase<XXServiceResource, RangerServiceResourceWithTags> {
+
+	private static final Logger LOG = LoggerFactory.getLogger(RangerServiceResourceWithTagsService.class);
+
+	public RangerServiceResourceWithTagsService() {
+        searchFields.add(new SearchField(SearchFilter.TAG_RESOURCE_ID,          "obj.id",                      DATA_TYPE.INTEGER,  SEARCH_TYPE.FULL));
+        searchFields.add(new SearchField(SearchFilter.TAG_SERVICE_ID,           "obj.serviceId",               DATA_TYPE.INTEGER,  SEARCH_TYPE.FULL));
+        searchFields.add(new SearchField(SearchFilter.TAG_SERVICE_NAME,         "service.name",                DATA_TYPE.STRING,   SEARCH_TYPE.FULL,    "XXService service", "obj.serviceId = service.id"));
+        searchFields.add(new SearchField(SearchFilter.TAG_SERVICE_NAME_PARTIAL, "service.name",                DATA_TYPE.STRING,   SEARCH_TYPE.PARTIAL, "XXService service", "obj.serviceId = service.id"));
+        searchFields.add(new SearchField(SearchFilter.TAG_RESOURCE_GUID,        "obj.guid",                    DATA_TYPE.STRING,   SEARCH_TYPE.FULL));
+        searchFields.add(new SearchField(SearchFilter.TAG_RESOURCE_SIGNATURE,   "obj.resourceSignature",       DATA_TYPE.STRING,   SEARCH_TYPE.FULL));
+        searchFields.add(new SearchField(SearchFilter.TAG_RESOURCE_IDS,         "obj.id",                      DATA_TYPE.INT_LIST, SEARCH_TYPE.FULL));
+        searchFields.add(new SearchField(SearchFilter.TAG_RESOURCE_ELEMENTS,    "obj.serviceResourceElements", DATA_TYPE.STRING,   SEARCH_TYPE.PARTIAL));
+        searchFields.add(new SearchField(SearchFilter.TAG_NAMES,                "tagDef.name",                 DATA_TYPE.STR_LIST, SEARCH_TYPE.FULL,    "XXTagResourceMap map, XXTag tag, XXTagDef tagDef", "obj.id = map.resourceId and map.tagId = tag.id and tag.type = tagDef.id"));
+
+        sortFields.add(new SortField(SearchFilter.TAG_RESOURCE_ID, "obj.id", true, SortField.SORT_ORDER.ASC));
+        sortFields.add(new SortField(SearchFilter.TAG_SERVICE_ID,  "obj.serviceId"));
+        sortFields.add(new SortField(SearchFilter.CREATE_TIME,     "obj.createTime"));
+        sortFields.add(new SortField(SearchFilter.UPDATE_TIME,     "obj.updateTime"));
+    }
+
+	@Override
+	protected XXServiceResource mapViewToEntityBean(RangerServiceResourceWithTags viewBean, XXServiceResource t, int OPERATION_CONTEXT) {
+		return null;
+	}
+
+	@Override
+	protected void validateForCreate(RangerServiceResourceWithTags vObj) {
+	}
+
+	@Override
+	protected void validateForUpdate(RangerServiceResourceWithTags vObj, XXServiceResource entityObj) {
+	}
+
+	public RangerServiceResourceWithTags getPopulatedViewObject(XXServiceResource xObj) {
+		return this.populateViewBean(xObj);
+	}
+
+	public RangerServiceResourceWithTagsList searchServiceResourcesWithTags(SearchFilter filter) {
+		LOG.debug("==> searchServiceResourcesWithTags({})", filter);
+
+		RangerServiceResourceWithTagsList   ret          = new RangerServiceResourceWithTagsList();
+		List<XXServiceResource>             xObjList     = super.searchResources(filter, searchFields, sortFields, ret);
+		List<RangerServiceResourceWithTags> resourceList = new ArrayList<>();
+
+		if (xObjList != null) {
+			for (XXServiceResource resource:xObjList) {
+				resourceList.add(getPopulatedViewObject(resource));
+			}
+		}
+
+		ret.setResourceList(resourceList);
+
+		LOG.debug("<== searchServiceResourcesWithTags({}): ret={}", filter, ret);
+
+		return ret;
+	}
+
+	@Override
+    protected RangerServiceResourceWithTags mapEntityToViewBean(RangerServiceResourceWithTags serviceResourceWithTags, XXServiceResource xxServiceResource) {
+		RangerServiceResourceWithTags ret = super.mapEntityToViewBean(serviceResourceWithTags, xxServiceResource);
+
+        if (StringUtils.isNotEmpty(xxServiceResource.getServiceResourceElements())) {
+            Map<String, RangerPolicyResource> serviceResourceElements = RangerTagDBRetriever.gsonBuilder.fromJson(xxServiceResource.getServiceResourceElements(), RangerServiceResourceService.subsumedDataType);
+
+            if (MapUtils.isNotEmpty(serviceResourceElements)) {
+                ret.setResourceElements(serviceResourceElements);
+            } else {
+                LOG.info("Empty serviceResourceElement in [" + ret + "]!!");
+            }
+        } else {
+            LOG.info("Empty string representing serviceResourceElements in [" + xxServiceResource + "]!!");
+        }
+
+        return ret;
+    }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceResourceWithTagsServiceBase.java b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceResourceWithTagsServiceBase.java
new file mode 100755
index 0000000..57cd20a
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceResourceWithTagsServiceBase.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.ranger.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ranger.authorization.utils.JsonUtils;
+import org.apache.ranger.entity.XXService;
+import org.apache.ranger.entity.XXServiceResource;
+import org.apache.ranger.plugin.model.RangerServiceResourceWithTags;
+import org.apache.ranger.plugin.store.PList;
+import org.apache.ranger.plugin.util.SearchFilter;
+
+public abstract class RangerServiceResourceWithTagsServiceBase<T extends XXServiceResource, V extends RangerServiceResourceWithTags> extends RangerBaseModelService<T, V> {
+
+	@Override
+	protected V mapEntityToViewBean(V vObj, T xObj) {
+		XXService xService = daoMgr.getXXService().getById(xObj.getServiceId());
+
+		vObj.setGuid(xObj.getGuid());
+		vObj.setVersion(xObj.getVersion());
+		vObj.setIsEnabled(xObj.getIsEnabled());
+		vObj.setServiceName(xService.getName());
+		vObj.setAssociatedTags(JsonUtils.jsonToRangerTagList(xObj.getTags()));
+
+		return vObj;
+	}
+
+	public PList<V> searchServiceResources(SearchFilter searchFilter) {
+		PList<V> retList       = new PList<V>();
+		List<V>  resourceList  = new ArrayList<V>();
+		List<T>  xResourceList = searchRangerObjects(searchFilter, searchFields, sortFields, retList);
+
+		for (T xResource : xResourceList) {
+			V taggedRes = populateViewBean(xResource);
+
+			resourceList.add(taggedRes);
+		}
+
+		retList.setList(resourceList);
+		retList.setResultSize(resourceList.size());
+		retList.setPageSize(searchFilter.getMaxRows());
+		retList.setStartIndex(searchFilter.getStartIndex());
+		retList.setSortType(searchFilter.getSortType());
+		retList.setSortBy(searchFilter.getSortBy());
+
+		return retList;
+	}
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/view/RangerServiceResourceWithTagsList.java b/security-admin/src/main/java/org/apache/ranger/view/RangerServiceResourceWithTagsList.java
new file mode 100644
index 0000000..a40953e
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/view/RangerServiceResourceWithTagsList.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ranger.view;
+
+import java.util.List;
+
+import org.apache.ranger.common.view.VList;
+import org.apache.ranger.plugin.model.RangerServiceResourceWithTags;
+import org.codehaus.jackson.annotate.JsonAutoDetect;
+import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+@JsonAutoDetect(getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, fieldVisibility = Visibility.ANY)
+@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+public class RangerServiceResourceWithTagsList extends VList {
+	private static final long serialVersionUID = 1L;
+
+	List<RangerServiceResourceWithTags> resourceList;
+
+	public RangerServiceResourceWithTagsList() {
+		super();
+	}
+
+	public RangerServiceResourceWithTagsList(List<RangerServiceResourceWithTags> objList) {
+		super(objList);
+
+		this.resourceList = objList;
+	}
+
+	public List<RangerServiceResourceWithTags> getResourceList() {
+		return resourceList;
+	}
+
+	public void setResourceList(List<RangerServiceResourceWithTags> resourceList) {
+		this.resourceList = resourceList;
+	}
+
+	@Override
+	public int getListSize() {
+		return (resourceList != null) ? resourceList.size() : 0;
+	}
+
+	@Override
+	public List<RangerServiceResourceWithTags> getList() {
+		return resourceList;
+	}
+}
diff --git a/security-admin/src/test/java/org/apache/ranger/biz/TestTagDBStore.java b/security-admin/src/test/java/org/apache/ranger/biz/TestTagDBStore.java
old mode 100644
new mode 100755
index d6ebbc5..acc9cab
--- a/security-admin/src/test/java/org/apache/ranger/biz/TestTagDBStore.java
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestTagDBStore.java
@@ -46,6 +46,7 @@
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerServiceConfigDef;
 import org.apache.ranger.plugin.model.RangerServiceResource;
+import org.apache.ranger.plugin.model.RangerServiceResourceWithTags;
 import org.apache.ranger.plugin.model.RangerTag;
 import org.apache.ranger.plugin.model.RangerTagDef;
 import org.apache.ranger.plugin.model.RangerTagResourceMap;
@@ -53,9 +54,11 @@
 import org.apache.ranger.plugin.util.SearchFilter;
 import org.apache.ranger.plugin.util.ServiceTags;
 import org.apache.ranger.service.RangerServiceResourceService;
+import org.apache.ranger.service.RangerServiceResourceWithTagsService;
 import org.apache.ranger.service.RangerTagDefService;
 import org.apache.ranger.service.RangerTagResourceMapService;
 import org.apache.ranger.service.RangerTagService;
+import org.apache.ranger.view.RangerServiceResourceWithTagsList;
 import org.junit.Assert;
 import org.junit.FixMethodOrder;
 import org.junit.Rule;
@@ -88,6 +91,9 @@
     RangerServiceResourceService rangerServiceResourceService;
 
     @Mock
+    RangerServiceResourceWithTagsService rangerServiceResourceWithTagsService;
+
+    @Mock
     RangerTagResourceMapService rangerTagResourceMapService;
 
     @Mock
@@ -1185,4 +1191,52 @@
 
         return xxTagResourceMap;
     }
-}
+
+    @Test
+    public void tesGetPaginatedServiceResourcesWithTags() throws Exception {
+        RangerServiceResourceWithTagsList rangerServiceResourceViewList = createRangerServiceResourceWithTagsViewList();
+        SearchFilter                      searchFilter                  = new SearchFilter();
+
+        Mockito.when(rangerServiceResourceWithTagsService.searchServiceResourcesWithTags(searchFilter)).thenReturn(rangerServiceResourceViewList);
+
+        RangerServiceResourceWithTagsList returnedRangerServiceResourcePList = tagDBStore.getPaginatedServiceResourcesWithTags(searchFilter);
+
+        Assert.assertNotNull(returnedRangerServiceResourcePList);
+        Assert.assertEquals(returnedRangerServiceResourcePList.getList().size(), 1);
+
+        RangerServiceResourceWithTags returnedRangerServiceResource = returnedRangerServiceResourcePList.getResourceList().get(0);
+
+        Assert.assertEquals(returnedRangerServiceResource.getId(), id);
+        Assert.assertEquals(returnedRangerServiceResource.getGuid(), gId);
+        Assert.assertNotNull(returnedRangerServiceResource.getAssociatedTags());
+        Assert.assertEquals(rangerServiceResourceViewList.getResourceList().get(0).getAssociatedTags().size(), returnedRangerServiceResource.getAssociatedTags().size());
+    }
+
+    private  RangerServiceResourceWithTagsList createRangerServiceResourceWithTagsViewList() {
+        RangerServiceResourceWithTagsList   rangerServiceResourceViewList = new RangerServiceResourceWithTagsList();
+        List<RangerServiceResourceWithTags> rangerServiceResourceList     = new ArrayList<>();
+        RangerServiceResourceWithTags       rangerServiceResource         = new RangerServiceResourceWithTags();
+        List<RangerTag>                     associatedTags                = new ArrayList<>();
+
+        associatedTags.add(createRangerTag());
+
+        rangerServiceResource.setId(id);
+        rangerServiceResource.setCreateTime(new Date());
+        rangerServiceResource.setGuid(gId);
+        rangerServiceResource.setVersion(lastKnownVersion);
+        rangerServiceResource.setServiceName(serviceName);
+        rangerServiceResource.setAssociatedTags(associatedTags);
+
+        rangerServiceResourceList.add(rangerServiceResource);
+
+        rangerServiceResourceViewList.setResourceList(rangerServiceResourceList);
+        rangerServiceResourceViewList.setPageSize(0);
+        rangerServiceResourceViewList.setResultSize(1);
+        rangerServiceResourceViewList.setSortBy("asc");
+        rangerServiceResourceViewList.setSortType("1");
+        rangerServiceResourceViewList.setStartIndex(0);
+        rangerServiceResourceViewList.setTotalCount(1);
+
+        return rangerServiceResourceViewList;
+    }
+}
\ No newline at end of file
diff --git a/security-admin/src/test/java/org/apache/ranger/rest/TestTagREST.java b/security-admin/src/test/java/org/apache/ranger/rest/TestTagREST.java
old mode 100644
new mode 100755
index 98d87bc..7165a30
--- a/security-admin/src/test/java/org/apache/ranger/rest/TestTagREST.java
+++ b/security-admin/src/test/java/org/apache/ranger/rest/TestTagREST.java
@@ -37,6 +37,7 @@
 import org.apache.ranger.entity.XXServiceDef;
 import org.apache.ranger.plugin.model.RangerService;
 import org.apache.ranger.plugin.model.RangerServiceResource;
+import org.apache.ranger.plugin.model.RangerServiceResourceWithTags;
 import org.apache.ranger.plugin.model.RangerTag;
 import org.apache.ranger.plugin.model.RangerTagDef;
 import org.apache.ranger.plugin.model.RangerTagResourceMap;
@@ -46,8 +47,10 @@
 import org.apache.ranger.plugin.util.SearchFilter;
 import org.apache.ranger.plugin.util.ServiceTags;
 import org.apache.ranger.service.RangerServiceResourceService;
+import org.apache.ranger.service.RangerServiceResourceWithTagsService;
 import org.apache.ranger.service.RangerTagDefService;
 import org.apache.ranger.service.RangerTagService;
+import org.apache.ranger.view.RangerServiceResourceWithTagsList;
 import org.junit.Assert;
 import org.junit.FixMethodOrder;
 import org.junit.Rule;
@@ -110,6 +113,9 @@
 	@Mock
 	RangerServiceResourceService resourceService;
 
+	@Mock
+	RangerServiceResourceWithTagsService serviceResourceWithTagsService;
+
 	@Rule
 	public ExpectedException thrown = ExpectedException.none();
 
@@ -1134,34 +1140,43 @@
 	}
 
 	@Test
-	public void test64getServiceResources() {
-		HttpServletRequest           request               = Mockito.mock(HttpServletRequest.class);
-		SearchFilter                 searchFilter          = new SearchFilter();
-		PList<RangerServiceResource> ret                   = new PList<RangerServiceResource>();
-		List<RangerServiceResource>  serviceResourceList   = new ArrayList<RangerServiceResource>();
-		RangerServiceResource        rangerServiceResource = new RangerServiceResource();
+	public void test64getServiceResourcesWithTags() {
+		HttpServletRequest                   request               = Mockito.mock(HttpServletRequest.class);
+		SearchFilter                         searchFilter          = new SearchFilter();
+		RangerServiceResourceWithTagsList    ret                   = new RangerServiceResourceWithTagsList();
+		List<RangerServiceResourceWithTags>  serviceResourceList   = new ArrayList<RangerServiceResourceWithTags>();
+		RangerServiceResourceWithTags        rangerServiceResource = new RangerServiceResourceWithTags();
+		List<RangerTag>                      associatedTags        = new ArrayList<RangerTag>();
+		RangerTag                            rangerTag             = new RangerTag();
+
+		rangerTag.setId(id);
+		rangerTag.setGuid(gId);
+		rangerTag.setType(name);
+		associatedTags.add(rangerTag);
 
 		rangerServiceResource.setId(id);
 		rangerServiceResource.setServiceName(serviceName);
+		rangerServiceResource.setAssociatedTags(associatedTags);
 		serviceResourceList.add(rangerServiceResource);
-		ret.setList(serviceResourceList);
+		ret.setResourceList(serviceResourceList);
 
-		Mockito.when(searchUtil.getSearchFilter(Mockito.any(HttpServletRequest.class), eq(resourceService.sortFields)))
-		       .thenReturn(searchFilter);
+		Mockito.when(searchUtil.getSearchFilter(Mockito.any(HttpServletRequest.class), eq(resourceService.sortFields))).thenReturn(searchFilter);
 
 		try {
-			Mockito.when(tagStore.getPaginatedServiceResources((SearchFilter) Mockito.any())).thenReturn(ret);
+			Mockito.when(tagStore.getPaginatedServiceResourcesWithTags(Mockito.any())).thenReturn(ret);
 		} catch (Exception e) {
 		}
 
-		PList<RangerServiceResource> result = tagREST.getServiceResources(request);
+		RangerServiceResourceWithTagsList result = tagREST.getServiceResourcesWithTags(request);
 
-		Assert.assertNotNull(result.getList().get(0).getId());
-		Assert.assertEquals(result.getList().get(0).getId(), serviceResourceList.get(0).getId());
-		Assert.assertEquals(result.getList().get(0).getServiceName(), serviceResourceList.get(0).getServiceName());
+		Assert.assertNotNull(result.getResourceList().get(0).getId());
+		Assert.assertEquals(result.getResourceList().get(0).getId(), serviceResourceList.get(0).getId());
+		Assert.assertEquals(result.getResourceList().get(0).getServiceName(), serviceResourceList.get(0).getServiceName());
+		Assert.assertEquals(result.getResourceList().get(0).getAssociatedTags().size(), 1);
+		Assert.assertEquals(result.getResourceList().get(0).getAssociatedTags().get(0).getType(), name);
 
 		try {
-			Mockito.verify(tagStore).getPaginatedServiceResources((SearchFilter) Mockito.any());
+			Mockito.verify(tagStore).getPaginatedServiceResourcesWithTags((SearchFilter) Mockito.any());
 		} catch (Exception e) {
 		}
 	}
@@ -1170,6 +1185,7 @@
 	public void test38createTagResourceMap() {
 		RangerTagResourceMap oldTagResourceMap = null;
 		RangerTagResourceMap newTagResourceMap = new RangerTagResourceMap();
+
 		newTagResourceMap.setTagId(id);
 		newTagResourceMap.setResourceId(id);
 		
@@ -1187,6 +1203,7 @@
 		}
 		
 		RangerTagResourceMap rangerTagResourceMap = tagREST.createTagResourceMap(tagGuid, resourceGuid, false);
+
 		Assert.assertEquals(rangerTagResourceMap.getTagId(), newTagResourceMap.getTagId());
 		Assert.assertEquals(rangerTagResourceMap.getResourceId(), newTagResourceMap.getResourceId());
 		
@@ -1194,10 +1211,12 @@
 			Mockito.verify(tagStore).getTagResourceMapForTagAndResourceGuid(tagGuid, resourceGuid);
 		} catch (Exception e) {
 		}
+
 		try {
 			Mockito.verify(validator).preCreateTagResourceMap(tagGuid, resourceGuid);
 		} catch (Exception e) {
 		}
+
 		try {
 			Mockito.verify(tagStore).createTagResourceMap(newTagResourceMap);
 		} catch (Exception e) {
@@ -1212,7 +1231,9 @@
 			Mockito.when(tagStore.getTagResourceMapForTagAndResourceGuid(tagGuid, resourceGuid)).thenReturn(oldTagResourceMap);
 		} catch (Exception e) {
 		}
+
 		Mockito.when(restErrorUtil.createRESTException(Mockito.anyInt(),Mockito.anyString(), Mockito.anyBoolean())).thenThrow(new WebApplicationException());
+
 		thrown.expect(WebApplicationException.class);
 		tagREST.createTagResourceMap(tagGuid, resourceGuid, false);