/** * Copyright 2016 Hortonworks. * * 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 com.hortonworks.registries.tag.client; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.hortonworks.registries.tag.Tag; import com.hortonworks.registries.tag.TaggedEntity; import com.hortonworks.registries.tag.dto.TagDto; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.media.multipart.MultiPartFeature; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; public class TagClient { private final Client client; private final String catalogRootUrl; private final String tagRootUrl; public TagClient(String catalogRootUrl) { this(catalogRootUrl, new ClientConfig()); } public TagClient(String catalogRootUrl, ClientConfig clientConfig) { this.catalogRootUrl = catalogRootUrl; this.tagRootUrl = catalogRootUrl + "/tags"; client = ClientBuilder.newClient(clientConfig); client.register(MultiPartFeature.class); } /** * Add a given {@link Tag} into the system. This should * also handle nested tags. * * @param tag the tag to add * @return the added tag */ public Tag addTag(Tag tag) { Response response = client.target(tagRootUrl).request().post(Entity.json(new TagDto(tag))); TagDto tagDto = getEntity(response, TagDto.class); return makeTag(tagDto); } /** * Adds a given {@link Tag}, or updates an existing tag in the system. * * @param tag the tag * @return the added or updated tag */ public Tag addOrUpdateTag(Tag tag) { String tagUpdateUrl = String.format("%s/%s", tagRootUrl, tag.getId()); Response response = client.target(tagUpdateUrl).request().put(Entity.json(new TagDto(tag))); TagDto tagDto = getEntity(response, TagDto.class); return makeTag(tagDto); } /** * Removes the tag associated with the tag id from the system. * This could throw a {@link RuntimeException} if the tag being * removed has entities associated with it. * * @param tagId the tag id * @return the removed tag */ public Tag removeTag(Long tagId) { String tagRemoveUrl = String.format("%s/%s/", tagRootUrl, tagId); Response response = client.target(tagRemoveUrl).request().delete(); TagDto tagDto = getEntity(response, TagDto.class); return makeTag(tagDto); } /** * Returns the Tag associated with the given tag id. * * @param tagId the tag id * @return the tag */ public Tag getTag(Long tagId) { Response response = client.target(String.format("%s/%s/", tagRootUrl, tagId)).request().get(); return makeTag(getEntity(response, TagDto.class)); } /** * List all the tags in the system. * * @return all the tags in the system. */ public List<Tag> listTags() { return makeTags(getEntities(client.target(String.format("%s", tagRootUrl)), TagDto.class)); } /** * Returns the list of tags matching the given query criteria. * * @param queryParams the query params * @return the tags matching the query params */ public List<Tag> listTags(Map<String, Object> queryParams) { if(queryParams.size() == 0) listTags(); String queryString = getQueryString(queryParams); return makeTags(getEntities(client.target(String.format("%s?%s", tagRootUrl, queryString)), TagDto.class)); } /** * * @param queryParams map of query parameters * @return query string */ private String getQueryString(Map<String, Object> queryParams) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, Object> entry : queryParams.entrySet()) { if(sb.length() > 0){ sb.append('&'); } sb.append(entry.getKey()).append('=').append(entry.getValue()); } return sb.toString(); } /** * Tags the entity with the given tag * * @param taggedEntity * @param tagId */ public void addTagForEntity(TaggedEntity taggedEntity, Long tagId) { String entityTagUrl = String.format("%s/%s/%s/%s/%s", tagRootUrl, tagId, "entities", taggedEntity.getNamespace(), taggedEntity.getId()); Response responseObject = client.target(entityTagUrl).request().post(Entity.json(taggedEntity)); handleErrorResponse(responseObject); } /** * Tags the entity with the given tags * * @param taggedEntity * @param tags */ public void addTagsForEntity(TaggedEntity taggedEntity, List<Tag> tags) { for (Tag tag: tags) { addTagForEntity(taggedEntity , tag.getId()); } } /** * Removes the given tag from the entity. * * @param taggedEntity * @param tagId */ public void removeTagForEntity(TaggedEntity taggedEntity, Long tagId) { String entityTagUrl = String.format("%s/%s/%s/%s/%s", tagRootUrl, tagId, "entities", taggedEntity.getNamespace(), taggedEntity.getId()); Response responseObject = client.target(entityTagUrl).request().delete(); handleErrorResponse(responseObject); } /** * Removes the given tag from the entity. * * @param taggedEntity entity for which tag should be removed * @param tag tag to remove */ public void removeTagForEntity(TaggedEntity taggedEntity, Tag tag) { removeTagForEntity(taggedEntity , tag.getId()); } /** * Removes the given tags from the entity. * @param taggedEntity entity for which tags to be removed * @param tags tags to remove */ public void removeTagsForEntity(TaggedEntity taggedEntity, List<Tag> tags) { for (Tag tag: tags) { removeTagForEntity(taggedEntity , tag); } } /** * Updates the tags for the given entity with the new set of tags * * @param taggedEntity * @param tags */ public void addOrUpdateTagsForEntity(TaggedEntity taggedEntity, List<Tag> tags) { List<Tag> existingTags = getTags(taggedEntity); updateTags(taggedEntity, getTagsToBeAdded(existingTags, tags), getTagsToBeRemoved(existingTags, tags)); } private List<Tag> getTagsToBeRemoved(List<Tag> existing, List<Tag> newList) { return Lists.newArrayList( Sets.difference(ImmutableSet.copyOf(existing), ImmutableSet.copyOf(newList))); } private List<Tag> getTagsToBeAdded(List<Tag> existing, List<Tag> newList) { return Lists.newArrayList( Sets.difference(ImmutableSet.copyOf(newList), ImmutableSet.copyOf(existing))); } private void updateTags(TaggedEntity taggedEntity, List<Tag> tagsToBeAdded, List<Tag> tagsToBeRemoved) { removeTagsForEntity(taggedEntity, tagsToBeRemoved); addTagsForEntity(taggedEntity, tagsToBeAdded); } /** * Return the list of all the tags associated with the given entity. * * @param taggedEntity * @return */ public List<Tag> getTags(TaggedEntity taggedEntity) { List<TagDto> tagDtos = getTagDtos(taggedEntity); return makeTags(tagDtos); } private List<TagDto> getTagDtos(TaggedEntity taggedEntity) { return getEntities(client.target(String.format("%s/%s/%s/%s/%s", catalogRootUrl, "taggedentities", taggedEntity.getNamespace(), taggedEntity.getId(), "tags")), TagDto.class); } /** * Gets all the entities under the given tag id * * @param tagId the tag id */ public List<TaggedEntity> getTaggedEntities(Long tagId) { return getEntities(client.target(String.format("%s/%s/%s", tagRootUrl, tagId , "entities")), TaggedEntity.class); } private <T> T getEntity(Response r, Class<T> clazz) { try { String response = r.readEntity(String.class); ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(response, clazz); } catch (IOException ex) { throw new RuntimeException(ex); } } private <T> List<T> getEntities(WebTarget target, Class<T> clazz) { String response = target.request(MediaType.APPLICATION_JSON_TYPE).get(String.class); List<T> entities = new ArrayList<>(); try { ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(response); Iterator<JsonNode> it = node.get("entities").elements(); while (it.hasNext()) { entities.add(mapper.treeToValue(it.next(), clazz)); } } catch (Exception ex) { throw new RuntimeException(ex); } return entities; } private String getResponseMessage(String response) { try { ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(response); return mapper.treeToValue(node.get("responseMessage"), String.class); } catch (Exception ex) { throw new RuntimeException(ex); } } private Tag makeTag(TagDto tagDto) { if (tagDto == null) return null; List<Tag> parentTags = new ArrayList<>(); if (tagDto.getTagIds() != null) { for (Long tagId : tagDto.getTagIds()) { Tag tag = getTag(tagId); if (tag == null) { throw new IllegalArgumentException("Tag with id " + tagId + " does not exist."); } parentTags.add(tag); } } Tag tag = new Tag(); tag.setId(tagDto.getId()); tag.setName(tagDto.getName()); tag.setDescription(tagDto.getDescription()); tag.setTimestamp(tagDto.getTimestamp()); tag.setTags(parentTags); return tag; } private List<Tag> makeTags(List<TagDto> tagDtos) { List<Tag> tags = new ArrayList<>(); for (TagDto tagDto : tagDtos) { tags.add(makeTag(tagDto)); } return tags; } private void handleErrorResponse(Response responseObject) { Response.Status.Family statusFamily = responseObject.getStatusInfo().getFamily(); if (statusFamily == Response.Status.Family.CLIENT_ERROR || statusFamily == Response.Status.Family.SERVER_ERROR) { String response = responseObject.readEntity(String.class); throw new RuntimeException("Error occurred while removing tag for entity : "+ getResponseMessage(response)); } } }