Repository: incubator-ranger Updated Branches: refs/heads/tag-policy 9c42a8a84 -> eb8740380
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/eb874038/tagsync/src/main/java/org/apache/ranger/source/atlas/AtlasNotificationMapper.java ---------------------------------------------------------------------- diff --git a/tagsync/src/main/java/org/apache/ranger/source/atlas/AtlasNotificationMapper.java b/tagsync/src/main/java/org/apache/ranger/source/atlas/AtlasNotificationMapper.java new file mode 100644 index 0000000..fd92ce1 --- /dev/null +++ b/tagsync/src/main/java/org/apache/ranger/source/atlas/AtlasNotificationMapper.java @@ -0,0 +1,391 @@ +/* + * 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.source.atlas; + +import org.apache.atlas.notification.entity.EntityNotification; +import org.apache.atlas.typesystem.api.Entity; +import org.apache.atlas.typesystem.api.Trait; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ranger.plugin.model.RangerPolicy; +import org.apache.ranger.plugin.model.RangerServiceResource; +import org.apache.ranger.plugin.model.RangerTag; +import org.apache.ranger.plugin.model.RangerTagDef; +import org.apache.ranger.plugin.util.ServiceTags; +import org.apache.ranger.process.TagSyncConfig; + +import java.util.*; + +class AtlasNotificationMapper { + private static final Log LOG = LogFactory.getLog(AtlasNotificationMapper.class); + + public static final String ENTITY_TYPE_HIVE_DB = "hive_db"; + public static final String ENTITY_TYPE_HIVE_TABLE = "hive_table"; + public static final String ENTITY_TYPE_HIVE_COLUMN = "hive_column"; + + public static final String RANGER_TYPE_HIVE_DB = "database"; + public static final String RANGER_TYPE_HIVE_TABLE = "table"; + public static final String RANGER_TYPE_HIVE_COLUMN = "column"; + + public static final String ENTITY_ATTRIBUTE_QUALIFIED_NAME = "qualifiedName"; + public static final String QUALIFIED_NAME_FORMAT_DELIMITER_STRING = "."; + + + private static Properties properties = null; + + public static ServiceTags processEntityNotification(EntityNotification entityNotification, Properties props) { + + ServiceTags ret = null; + properties = props; + + try { + if (isEntityMappable(entityNotification.getEntity())) { + ret = createServiceTags(entityNotification); + } + } catch (Exception exception) { + LOG.error("createServiceTags() failed!! ", exception); + } + return ret; + } + + static private boolean isEntityMappable(Entity entity) { + boolean ret = false; + + String entityTypeName = entity.getTypeName(); + + if (StringUtils.isNotBlank(entityTypeName)) { + if (StringUtils.equals(entityTypeName, ENTITY_TYPE_HIVE_DB) || + StringUtils.equals(entityTypeName, ENTITY_TYPE_HIVE_TABLE) || + StringUtils.equals(entityTypeName, ENTITY_TYPE_HIVE_COLUMN)) { + ret = true; + } + } + return ret; + } + + static private ServiceTags createServiceTags(EntityNotification entityNotification) throws Exception { + + ServiceTags ret = null; + + EntityNotification.OperationType opType = entityNotification.getOperationType(); + Entity entity = entityNotification.getEntity(); + + String opName = entityNotification.getOperationType().name(); + switch (opType) { + case ENTITY_CREATED: { + ret = getServiceTags(entity, opType); + break; + } + case ENTITY_UPDATED: { + ret = handleEntityUpdate(entity); + break; + } + case TRAIT_ADDED: { + ret = getServiceTags(entity, opType); + break; + } + case TRAIT_DELETED: { + ret = handleTraitDelete(entity); + break; + } + default: + LOG.error("Unknown notification received. Will not be handled, notificationType=" + opName); + } + + return ret; + } + + static private ServiceTags getServiceTags(Entity entity, EntityNotification.OperationType opType) throws Exception { + ServiceTags ret = null; + + + List<RangerServiceResource> serviceResources = new ArrayList<RangerServiceResource>(); + + RangerServiceResource serviceResource = getServiceResource(entity, opType); + serviceResources.add(serviceResource); + + Map<Long, RangerTag> tags = getTags(entity); + + Map<Long, RangerTagDef> tagDefs = getTagDefs(tags, EntityNotification.OperationType.ENTITY_CREATED); + + Map<Long, List<Long>> resourceIdToTagIds = null; + + if (MapUtils.isNotEmpty(tags)) { + resourceIdToTagIds = new HashMap<Long, List<Long>>(); + + List<Long> tagList = new ArrayList<Long>(); + for (Map.Entry<Long, RangerTag> entry : tags.entrySet()) { + tagList.add(entry.getKey()); + } + resourceIdToTagIds.put(1L, tagList); + } + + ret = new ServiceTags(); + + ret.setOp(ServiceTags.OP_ADD_OR_UPDATE); + ret.setServiceName(serviceResource.getServiceName()); + ret.setServiceResources(serviceResources); + ret.setTagDefinitions(tagDefs); + ret.setTags(tags); + ret.setResourceToTagIds(resourceIdToTagIds); + + return ret; + } + + + static private RangerServiceResource getServiceResource(Entity entity, EntityNotification.OperationType opType) throws Exception { + + RangerServiceResource ret = null; + + Map<String, RangerPolicy.RangerPolicyResource> elements = null; + String serviceName = null; + + if (opType == EntityNotification.OperationType.ENTITY_CREATED) { + + elements = new HashMap<String, RangerPolicy.RangerPolicyResource>(); + + //String[] components = getQualifiedNameComponents(entity); + String[] components = getTempNameComponents(entity); + // components should contain qualifiedName, instanceName, dbName, tableName, columnName in that order + + + String entityTypeName = entity.getTypeName(); + + String instanceName, dbName, tableName, columnName; + + if (components.length > 1) { + instanceName = components[1]; + serviceName = getServiceName(instanceName, entityTypeName); + } + + if (StringUtils.equals(entityTypeName, ENTITY_TYPE_HIVE_DB)) { + if (components.length > 2) { + dbName = components[2]; + RangerPolicy.RangerPolicyResource dbPolicyResource = new RangerPolicy.RangerPolicyResource(dbName); + elements.put(RANGER_TYPE_HIVE_DB, dbPolicyResource); + + } else { + LOG.error("invalid qualifiedName for HIVE_DB, qualifiedName=" + components[0]); + } + } else if (StringUtils.equals(entityTypeName, ENTITY_TYPE_HIVE_TABLE)) { + if (components.length > 3) { + dbName = components[2]; + tableName = components[3]; + RangerPolicy.RangerPolicyResource dbPolicyResource = new RangerPolicy.RangerPolicyResource(dbName); + elements.put(RANGER_TYPE_HIVE_DB, dbPolicyResource); + RangerPolicy.RangerPolicyResource tablePolicyResource = new RangerPolicy.RangerPolicyResource(tableName); + elements.put(RANGER_TYPE_HIVE_TABLE, tablePolicyResource); + } else { + LOG.error("invalid qualifiedName for HIVE_TABLE, qualifiedName=" + components[0]); + } + } else if (StringUtils.equals(entityTypeName, ENTITY_TYPE_HIVE_COLUMN)) { + LOG.error("HIVE_COLUMN creation is not handled."); + throw new Exception("HIVE_COLUMN entity-creation not implemented"); + /* + if (components.length > 4) { + dbName = components[2]; + tableName = components[3]; + columnName = components[4]; + RangerPolicy.RangerPolicyResource dbPolicyResource = new RangerPolicy.RangerPolicyResource(dbName); + elements.put(RANGER_TYPE_HIVE_DB, dbPolicyResource); + RangerPolicy.RangerPolicyResource tablePolicyResource = new RangerPolicy.RangerPolicyResource(tableName); + elements.put(RANGER_TYPE_HIVE_TABLE, tablePolicyResource); + RangerPolicy.RangerPolicyResource columnPolicyResource = new RangerPolicy.RangerPolicyResource(tableName); + elements.put(RANGER_TYPE_HIVE_COLUMN, columnPolicyResource); + } else { + LOG.error("invalid qualifiedName for HIVE_COLUMN, qualifiedName=" + components[0]); + } + */ + } + } + + ret = new RangerServiceResource(); + ret.setGuid(entity.getId().getGuid()); + ret.setId(1L); + ret.setServiceName(serviceName); + ret.setResourceElements(elements); + + return ret; + } + + static private Map<Long, RangerTag> getTags(Entity entity) { + Map<Long, RangerTag> ret = null; + + Map<String, ? extends Trait> traits = entity.getTraits(); + + if (MapUtils.isNotEmpty(traits)) { + ret = new HashMap<Long, RangerTag>(); + long index = 1; + + for (Map.Entry<String, ? extends Trait> entry : traits.entrySet()) { + String traitName = entry.getKey(); + Trait trait = entry.getValue(); + + Map<String, Object> attrValues = trait.getValues(); + + Map<String, String> tagAttrValues = new HashMap<String, String>(); + + for (Map.Entry<String, Object> attrValueEntry : attrValues.entrySet()) { + String attrName = attrValueEntry.getKey(); + Object attrValue = attrValueEntry.getValue(); + try { + String strValue = String.class.cast(attrValue); + tagAttrValues.put(attrName, strValue); + } catch (ClassCastException exception) { + LOG.error("Cannot cast attribute-value to String, skipping... attrName=" + attrName); + } + } + + RangerTag tag = new RangerTag(); + + tag.setGuid(entity.getId().getGuid() + "-" + traitName); + tag.setType(traitName); + tag.setAttributes(tagAttrValues); + + ret.put(index++, tag); + } + } + + return ret; + } + + static private Map<Long, RangerTagDef> getTagDefs(Map<Long, RangerTag> tags, EntityNotification.OperationType opType) { + + Map<Long, RangerTagDef> ret = null; + + if (opType == EntityNotification.OperationType.ENTITY_CREATED || opType == EntityNotification.OperationType.TRAIT_ADDED) { + if (MapUtils.isNotEmpty(tags)) { + ret = new HashMap<Long, RangerTagDef>(); + for (Map.Entry<Long, RangerTag> entry : tags.entrySet()) { + RangerTagDef tagDef = new RangerTagDef(); + tagDef.setName(entry.getValue().getType()); + tagDef.setId(entry.getKey()); + ret.put(entry.getKey(), tagDef); + } + } + } + + return ret; + } + + static private String[] getQualifiedNameComponents(Entity entity) { + String ret[] = new String[5]; + + String qualifiedName = getAttribute(entity.getValues(), ENTITY_ATTRIBUTE_QUALIFIED_NAME, String.class); + + String nameHierarchy[] = qualifiedName.split(QUALIFIED_NAME_FORMAT_DELIMITER_STRING); + + int hierarchyLevels = nameHierarchy.length; + + if (LOG.isDebugEnabled()) { + LOG.debug("----- Entity-Id:" + entity.getId().getGuid()); + LOG.debug("----- Entity-Type-Name:" + entity.getTypeName()); + LOG.debug("----- Entity-Qualified-Name:" + qualifiedName); + LOG.debug("----- Entity-Qualified-Name-Components -----"); + for (int i = 0; i < hierarchyLevels; i++) { + LOG.debug("----- Index:" + i + " Value:" + nameHierarchy[i]); + } + } + + int i; + for (i = 0; i < ret.length; i++) { + ret[i] = null; + } + ret[0] = qualifiedName; + + for (i = 0; i < hierarchyLevels; i++) { + ret[i+1] = nameHierarchy[i]; + } + return ret; + } + + static private String getServiceName(String instanceName, String entityTypeName) { + // Parse entityTypeName to get the Apache-component Name + String apacheComponents[] = entityTypeName.split("_"); + String apacheComponent = null; + if (apacheComponents.length > 0) { + apacheComponent = apacheComponents[0].toLowerCase(); + } + + return TagSyncConfig.getServiceName(apacheComponent, instanceName, properties); + } + + static private <T> T getAttribute(Map<String, Object> map, String name, Class<T> type) { + return type.cast(map.get(name)); + } + + // Temporary stuff, until qualifiedName is implemented by Atlas + static private String[] getTempNameComponents(Entity entity) { + String ret[] = new String[4]; + if (StringUtils.equals(entity.getTypeName(), ENTITY_TYPE_HIVE_DB)) { + ret[1] = getAttribute(entity.getValues(), "clusterName", String.class); + ret[2] = getAttribute(entity.getValues(), "name", String.class); + ret[3] = null; + ret[0] = ret[1] + "@" + ret[2]; + } else if (StringUtils.equals(entity.getTypeName(), ENTITY_TYPE_HIVE_TABLE)) { + String qualifiedName = getAttribute(entity.getValues(), "name", String.class); + String nameHierarchy[] = qualifiedName.split(".@"); + + int hierarchyLevels = nameHierarchy.length; + + if (LOG.isDebugEnabled()) { + LOG.debug("----- Entity-Id:" + entity.getId().getGuid()); + LOG.debug("----- Entity-Type-Name:" + entity.getTypeName()); + LOG.debug("----- Entity-Qualified-Name:" + qualifiedName); + LOG.debug("----- Entity-Qualified-Name-Components -----"); + for (int i = 0; i < hierarchyLevels; i++) { + LOG.debug("----- Index:" + i + " Value:" + nameHierarchy[i]); + } + } + + int i; + for (i = 0; i < ret.length; i++) { + ret[i] = null; + } + ret[0] = qualifiedName; + if (hierarchyLevels > 2) { + ret[1] = nameHierarchy[2]; + } + if (hierarchyLevels > 1) { + ret[2] = nameHierarchy[1]; + } + if (hierarchyLevels > 0) { + ret[3] = nameHierarchy[0]; + } + + + } + return ret; + } + + + static private ServiceTags handleEntityUpdate(Entity entity) throws Exception { + + throw new Exception("Not implemented"); + + } + + static private ServiceTags handleTraitDelete(Entity entity) throws Exception { + + throw new Exception("Not implemented"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/eb874038/tagsync/src/main/java/org/apache/ranger/source/atlas/TagAtlasSource.java ---------------------------------------------------------------------- diff --git a/tagsync/src/main/java/org/apache/ranger/source/atlas/TagAtlasSource.java b/tagsync/src/main/java/org/apache/ranger/source/atlas/TagAtlasSource.java new file mode 100644 index 0000000..d9142c7 --- /dev/null +++ b/tagsync/src/main/java/org/apache/ranger/source/atlas/TagAtlasSource.java @@ -0,0 +1,588 @@ +/* + * 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.source.atlas; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import org.apache.atlas.typesystem.EntityImpl; +import org.apache.atlas.typesystem.IdImpl; +import org.apache.atlas.typesystem.TraitImpl; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.atlas.notification.NotificationModule; +import org.apache.atlas.notification.entity.EntityNotification; +import org.apache.atlas.notification.entity.EntityNotificationConsumer; +import org.apache.atlas.notification.entity.EntityNotificationConsumerProvider; +import org.apache.atlas.typesystem.api.Entity; +import org.apache.atlas.typesystem.api.Trait; +import org.apache.ranger.admin.client.datatype.RESTResponse; +import org.apache.ranger.model.TagSink; +import org.apache.ranger.model.TagSource; +import org.apache.ranger.plugin.util.RangerRESTClient; +import org.apache.ranger.plugin.util.RangerRESTUtils; +import org.apache.ranger.plugin.util.ServiceTags; +import org.apache.ranger.process.TagSyncConfig; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class TagAtlasSource implements TagSource { + private static final Log LOG = LogFactory.getLog(TagAtlasSource.class); + + + private final Map<String, Entity> entities = new LinkedHashMap<>(); + private TagSink tagSink; + private Properties properties; + private ConsumerRunnable consumerTask; + + @Override + public boolean initialize(Properties properties) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> TagAtlasSource.initialize()"); + } + + boolean ret = true; + + if (properties == null || MapUtils.isEmpty(properties)) { + LOG.error("No properties specified for TagFileSource initialization"); + this.properties = new Properties(); + } else { + this.properties = properties; + } + + + NotificationModule notificationModule = new NotificationModule(); + + Injector injector = Guice.createInjector(notificationModule); + + EntityNotificationConsumerProvider consumerProvider = injector.getInstance(EntityNotificationConsumerProvider.class); + + consumerTask = new ConsumerRunnable(consumerProvider.get()); + + //ExecutorService executorService = Executors.newFixedThreadPool(1); + + //executorService.submit(new ConsumerRunnable(consumerProvider.get())); + + + if (LOG.isDebugEnabled()) { + LOG.debug("<== TagAtlasSource.initialize(), result=" + ret); + } + return ret; + } + + @Override + public void setTagSink(TagSink sink) { + if (sink == null) { + LOG.error("Sink is null!!!"); + } else { + this.tagSink = sink; + } + } + + @Override + public Thread start() { + Thread consumerThread = null; + if (consumerTask == null) { + LOG.error("No consumerTask!!!"); + } else { + consumerThread = new Thread(consumerTask); + consumerThread.setDaemon(true); + consumerThread.start(); + } + return consumerThread; + } + + @Override + public void updateSink() throws Exception { + } + + @Override + public boolean isChanged() { + return true; + } + + // ----- inner class : ConsumerRunnable ------------------------------------ + + private class ConsumerRunnable implements Runnable { + + private final EntityNotificationConsumer consumer; + + private ConsumerRunnable(EntityNotificationConsumer consumer) { + this.consumer = consumer; + } + + + // ----- Runnable -------------------------------------------------------- + + @Override + public void run() { + while (consumer.hasNext()) { + try { + EntityNotification notification = consumer.next(); + Entity entity = notification.getEntity(); + printNotification(notification); + ServiceTags serviceTags = AtlasNotificationMapper.processEntityNotification(notification, properties); + if (serviceTags == null) { + LOG.error("Failed to map Atlas notification to ServiceTags structure"); + } else { + if (LOG.isDebugEnabled()) { + String serviceTagsJSON = new Gson().toJson(serviceTags); + LOG.debug("Atlas notification mapped to serviceTags=" + serviceTagsJSON); + } + + try { + tagSink.uploadServiceTags(serviceTags); + } catch (Exception exception) { + LOG.error("uploadServiceTags() failed..", exception); + } + } + } catch(Exception e){ + LOG.error("Exception encountered when processing notification:", e); + } + } + } + + public void printNotification(EntityNotification notification) { + Entity entity = notification.getEntity(); + if (LOG.isDebugEnabled()) { + LOG.debug("Notification-Type: " + notification.getOperationType().name()); + LOG.debug("Entity-Id: " + entity.getId().getGuid()); + LOG.debug("Entity-Type: " + entity.getTypeName()); + + LOG.debug("----------- Entity Values ----------"); + + + for (Map.Entry<String, Object> entry : entity.getValues().entrySet()) { + LOG.debug(" Name:" + entry.getKey()); + Object value = entry.getValue(); + LOG.debug(" Value:" + value); + } + + LOG.debug("----------- Entity Traits ----------"); + + + for (Map.Entry<String, ? extends Trait> entry : entity.getTraits().entrySet()) { + LOG.debug(" Trait-Name:" + entry.getKey()); + Trait trait = entry.getValue(); + LOG.debug(" Trait-Type:" + trait.getTypeName()); + Map<String, Object> traitValues = trait.getValues(); + for (Map.Entry<String, Object> valueEntry : traitValues.entrySet()) { + LOG.debug(" Trait-Value-Name:" + valueEntry.getKey()); + LOG.debug(" Trait-Value:" + valueEntry.getValue()); + } + } + + } + } + + } + + public void printAllEntities() { + try { + new AtlasUtility().getAllEntities(); + } + catch(java.io.IOException ioException) { + LOG.error("Caught IOException while retrieving Atlas Entities:", ioException); + } + } + + // update the set of entities with current from Atlas + public void refreshAllEntities() { + if (LOG.isDebugEnabled()) { + LOG.debug("==> TagAtlasSource.refreshAllEntities()"); + } + AtlasUtility atlasUtility = new AtlasUtility(); + + try { + entities.putAll(atlasUtility.getAllEntities()); + } catch (IOException e) { + LOG.error("getAllEntities() failed", e); + } + if (LOG.isDebugEnabled()) { + LOG.debug("<== TagAtlasSource.refreshAllEntities()"); + } + } + + // Inner class AtlasUtil + + /** + * Atlas utility. + */ + @SuppressWarnings("unchecked") + private class AtlasUtility { + + /** + * Atlas APIs + */ + public static final String API_ATLAS_TYPES = "api/atlas/types"; + public static final String API_ATLAS_ENTITIES = "api/atlas/entities?type="; + public static final String API_ATLAS_ENTITY = "api/atlas/entities/"; + public static final String API_ATLAS_TYPE = "api/atlas/types/"; + + /** + * API Response Attributes + */ + public static final String RESULTS_ATTRIBUTE = "results"; + public static final String DEFINITION_ATTRIBUTE = "definition"; + public static final String VALUES_ATTRIBUTE = "values"; + public static final String TRAITS_ATTRIBUTE = "traits"; + public static final String TYPE_NAME_ATTRIBUTE = "typeName"; + public static final String TRAIT_TYPES_ATTRIBUTE = "traitTypes"; + public static final String SUPER_TYPES_ATTRIBUTE = "superTypes"; + public static final String ATTRIBUTE_DEFINITIONS_ATTRIBUTE = "attributeDefinitions"; + public static final String NAME_ATTRIBUTE = "name"; + + private Type mapType = new TypeToken<Map<String, Object>>(){}.getType(); + + private RangerRESTClient restClient; + + + // ----- Constructors ------------------------------------------------------ + + /** + * Construct an AtlasUtility + * + */ + public AtlasUtility() { + + String url = TagSyncConfig.getAtlasEndpoint(properties); + String sslConfigFileName = TagSyncConfig.getAtlasSslConfigFileName(properties); + + + if(LOG.isDebugEnabled()) { + LOG.debug("Initializing RangerRestClient with (url=" + url + ", sslConfigFileName" + sslConfigFileName + ")"); + } + + restClient = new RangerRESTClient(url, sslConfigFileName); + + if(LOG.isDebugEnabled()) { + LOG.debug("Initialized RangerRestClient with (url=" + url + ", sslConfigFileName=" + sslConfigFileName + ")"); + } + } + + + // ----- AtlasUtility ------------------------------------------------------ + + /** + * Get all of the entities defined in Atlas. + * + * @return a mapping of GUIDs to Atlas entities + * + * @throws IOException if there is an error communicating with Atlas + */ + public Map<String, Entity> getAllEntities() throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("==> TagAtlasSource.getAllEntities()"); + } + Map<String, Entity> entities = new LinkedHashMap<>(); + + Map<String, Object> typesResponse = atlasAPI(API_ATLAS_TYPES); + + List<String> types = getAttribute(typesResponse, RESULTS_ATTRIBUTE, List.class); + + for (String type : types) { + + Map<String, Object> entitiesResponse = atlasAPI(API_ATLAS_ENTITIES + type); + + List<String> guids = getAttribute(entitiesResponse, RESULTS_ATTRIBUTE, List.class); + + for (String guid : guids) { + + if (StringUtils.isNotBlank(guid)) { + + Map<Trait, Map<String, ? extends Trait>> traitSuperTypes = new HashMap<>(); + + Map<String, Object> entityResponse = atlasAPI(API_ATLAS_ENTITY + guid); + + if (entityResponse.containsKey(DEFINITION_ATTRIBUTE)) { + String definitionJSON = getAttribute(entityResponse, DEFINITION_ATTRIBUTE, String.class); + + LOG.info("{"); + LOG.info(" \"entity-id\":" + guid + ","); + LOG.info(" \"entity-definition\":" + definitionJSON); + LOG.info("}"); + + Map<String, Object> definition = new Gson().fromJson(definitionJSON, mapType); + + Map<String, Object> values = getAttribute(definition, VALUES_ATTRIBUTE, Map.class); + Map<String, Object> traits = getAttribute(definition, TRAITS_ATTRIBUTE, Map.class); + String typeName = getAttribute(definition, TYPE_NAME_ATTRIBUTE, String.class); + + LOG.info("Received entity(typeName=" + typeName + ", id=" + guid + ")"); + + + Map<String, TraitImpl> traitMap = new HashMap<>(); + + if (MapUtils.isNotEmpty(traits)) { + + LOG.info("Traits for entity(typeName=" + typeName + ", id=" + guid + ") ------ "); + + for (Map.Entry<String, Object> entry : traits.entrySet()) { + + Map<String, Object> trait = (Map<String, Object>) entry.getValue(); + + Map<String, Object> traitValues = getAttribute(trait, VALUES_ATTRIBUTE, Map.class); + String traitTypeName = getAttribute(trait, TYPE_NAME_ATTRIBUTE, String.class); + + Map<String, TraitImpl> superTypes = getTraitSuperTypes(getTraitType(traitTypeName), traitValues); + + TraitImpl trait1 = new TraitImpl(traitTypeName, traitValues, superTypes); + + traitSuperTypes.put(trait1, superTypes); + + traitMap.put(entry.getKey(), trait1); + + + LOG.info(" Trait(typeName=" + traitTypeName + ")"); + + } + } else { + LOG.info("No traits for entity(typeName=" + typeName + ", id=" + guid + ")"); + } + EntityImpl entity = new EntityImpl(new IdImpl(guid, 0), typeName, values, traitMap); + + showEntity(entity); + + entities.put(guid, entity); + + } + } + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("==> TagAtlasSource.getAllEntities()"); + } + return entities; + } + + + // ----- helper methods ---------------------------------------------------- + + private Map<String, Object> getTraitType(String traitName) + throws IOException { + + Map<String, Object> typeResponse = atlasAPI(API_ATLAS_TYPE + traitName); + + if (typeResponse.containsKey(DEFINITION_ATTRIBUTE)) { + String definitionJSON = getAttribute(typeResponse, DEFINITION_ATTRIBUTE, String.class); + + Map<String, Object> definition = new Gson().fromJson(definitionJSON, mapType); + + List traitTypes = getAttribute(definition, TRAIT_TYPES_ATTRIBUTE, List.class); + + if (traitTypes.size() > 0) { + return (Map<String, Object>) traitTypes.get(0); + } + } + return null; + } + + private Map<String, TraitImpl> getTraitSuperTypes(Map<String, Object> traitType, Map<String, Object> values) + throws IOException { + + Map<String, TraitImpl> superTypes = new HashMap<>(); + + if (traitType != null) { + + List<String> superTypeNames = getAttribute(traitType, SUPER_TYPES_ATTRIBUTE, List.class); + + for (String superTypeName : superTypeNames) { + + Map<String, Object> superTraitType = getTraitType(superTypeName); + + if (superTraitType != null) { + List<Map<String, Object>> attributeDefinitions = (List) superTraitType.get(ATTRIBUTE_DEFINITIONS_ATTRIBUTE); + + Map<String, Object> superTypeValues = new HashMap<>(); + for (Map<String, Object> attributeDefinition : attributeDefinitions) { + + String attributeName = attributeDefinition.get(NAME_ATTRIBUTE).toString(); + if (values.containsKey(attributeName)) { + superTypeValues.put(attributeName, values.get(attributeName)); + } + } + + superTypes.put(superTypeName, + //new TraitImpl(getTraitSuperTypes(superTraitType, superTypeValues), superTypeValues, superTypeName)); + new TraitImpl(superTypeName, superTypeValues, getTraitSuperTypes(superTraitType, superTypeValues))); + } + } + } + return superTypes; + } + + /* + private Map<String, Object> atlasAPI(String endpoint) throws IOException { + InputStream in = streamProvider.readFrom(atlasEndpoint + endpoint, "GET", (String) null, Collections.<String, String>emptyMap()); + return new Gson().fromJson(IOUtils.toString(in, "UTF-8"), mapType); + } + */ + + private Map<String, Object> atlasAPI(String endpoint) { + if (LOG.isDebugEnabled()) { + LOG.debug("==> TagAtlasSource.atlasAPI(" + endpoint +")"); + } + // Create a REST client and perform a get on it + Map<String, Object> ret = new HashMap<String, Object>(); + + WebResource webResource = restClient.getResource(endpoint); + + ClientResponse response = webResource.accept(RangerRESTUtils.REST_MIME_TYPE_JSON).get(ClientResponse.class); + + if(response != null && response.getStatus() == 200) { + ret = response.getEntity(ret.getClass()); + } else { + LOG.error("Atlas REST call returned with response={" + response +"}"); + + RESTResponse resp = RESTResponse.fromClientResponse(response); + LOG.error("Error getting Atlas Entity. request=" + webResource.toString() + + ", response=" + resp.toString()); + } + if (LOG.isDebugEnabled()) { + LOG.debug("<== TagAtlasSource.atlasAPI(" + endpoint + ")"); + } + return ret; + } + + private <T> T getAttribute(Map<String, Object> map, String name, Class<T> type) { + return type.cast(map.get(name)); + } + + + + public void showEntity(Entity entity) { + + LOG.debug("Entity-id :" + entity.getId()); + + LOG.debug("Type: " + entity.getTypeName()); + + LOG.debug("----- Values -----"); + + for (Map.Entry<String, Object> entry : entity.getValues().entrySet()) { + LOG.debug(" Name: " + entry.getKey() + ""); + Object value = entry.getValue(); + LOG.debug(" Value: " + getValue(value, entities.keySet())); + } + + LOG.debug("----- Traits -----"); + + for (String traitName : entity.getTraits().keySet()) { + LOG.debug(" Name:" + entity.getId() + ", trait=" + traitName + ">" + traitName); + } + + } + + public void showTrait(Entity entity, String traitId) { + + String[] traitNames = traitId.split(","); + + Trait trait = entity.getTraits().get(traitNames[0]); + + for (int i = 1; i < traitNames.length; ++i ) { + trait = trait.getSuperTypes().get(traitNames[i]); + } + + String typeName = trait.getTypeName(); + + LOG.debug("Trait " + typeName + " for Entity id=" + entity.getId()); + + LOG.debug("Type: " + typeName); + + LOG.debug("----- Values ------"); + + for (Map.Entry<String, Object> entry : trait.getValues().entrySet()) { + LOG.debug("Name:" + entry.getKey()); + Object value = entry.getValue(); + LOG.debug("Value:" + getValue(value, entities.keySet())); + } + + LOG.debug("Super Traits"); + + + for (String traitName : trait.getSuperTypes().keySet()) { + LOG.debug("Name=" + entity.getId() + "&trait=" + traitId + "," + traitName + ">" + traitName); + } + } + + // resolve the given value if necessary + private String getValue(Object value, Set<String> ids) { + if (value == null) { + return ""; + } + String idString = getIdValue(value, ids); + if (idString != null) { + return idString; + } + + idString = getIdListValue(value, ids); + if (idString != null) { + return idString; + } + + return value.toString(); + } + // get an id from the given value; return null if the value is not an id type + private String getIdValue(Object value, Set<String> ids) { + if (value instanceof Map) { + Map map = (Map) value; + if (map.size() == 3 && map.containsKey("id")){ + String id = map.get("id").toString(); + if (ids.contains(id)) { + return id; + } + } + } + return null; + } + // get an id list from the given value; return null if the value is not an id list type + private String getIdListValue(Object value, Set<String> ids) { + if (value instanceof List) { + List list = (List) value; + if (list.size() > 0) { + StringBuilder sb = new StringBuilder(); + for (Object o : list) { + String idString = getIdValue(o, ids); + if (idString == null) { + return value.toString(); + } + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(idString); + } + return sb.toString(); + } + } + return null; + } + + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/eb874038/tagsync/src/main/java/org/apache/ranger/source/file/TagFileSource.java ---------------------------------------------------------------------- diff --git a/tagsync/src/main/java/org/apache/ranger/source/file/TagFileSource.java b/tagsync/src/main/java/org/apache/ranger/source/file/TagFileSource.java index 2952edb..25083de 100644 --- a/tagsync/src/main/java/org/apache/ranger/source/file/TagFileSource.java +++ b/tagsync/src/main/java/org/apache/ranger/source/file/TagFileSource.java @@ -21,35 +21,21 @@ package org.apache.ranger.source.file; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ranger.model.TagSink; import org.apache.ranger.model.TagSource; -import org.apache.ranger.plugin.model.RangerServiceResource; -import org.apache.ranger.plugin.model.RangerTag; -import org.apache.ranger.plugin.model.RangerTagDef; -import org.apache.ranger.plugin.model.RangerTagResourceMap; import org.apache.ranger.plugin.util.ServiceTags; import org.apache.ranger.process.TagSyncConfig; import java.io.*; import java.util.Date; -import java.util.List; -import java.util.Map; import java.util.Properties; -/** - * Created by akulkarni on 9/11/15. - */ public class TagFileSource implements TagSource, Runnable { private static final Log LOG = LogFactory.getLog(TagFileSource.class); - public static final String CREATE_OR_UPDATE_SERVICETAGS_OP = "CREATE_OR_UPDATE"; - public static final String DELETE_SERVICETAGS_OP = "DELETE"; - private String sourceFileName; private long lastModifiedTimeInMillis = 0L; @@ -118,8 +104,15 @@ public class TagFileSource implements TagSource, Runnable { } @Override - public void start() { - (new Thread(this)).start(); + public Thread start() { + + Thread fileMonitoringThread = null; + + fileMonitoringThread = new Thread(this); + fileMonitoringThread.setDaemon(true); + fileMonitoringThread.start(); + + return fileMonitoringThread; } @Override @@ -155,12 +148,10 @@ public class TagFileSource implements TagSource, Runnable { } catch (Throwable t) { LOG.error("tag-sync thread got an error", t); - shutdownFlag = true; } - } - LOG.error("Shutting down the Tag-file-source thread"); + LOG.info("Shutting down the Tag-file-source thread"); if (LOG.isDebugEnabled()) { LOG.debug("<== TagFileSource.run()"); @@ -176,9 +167,7 @@ public class TagFileSource implements TagSource, Runnable { ServiceTags serviceTags = readFromFile(); if (serviceTags != null) { - tagSink.uploadServiceTags(serviceTags); - } else { LOG.error("Could not read ServiceTags from file"); } @@ -187,138 +176,6 @@ public class TagFileSource implements TagSource, Runnable { } } - /* - private void createTagObjects(ServiceTags serviceTags) { - if (LOG.isDebugEnabled()) { - LOG.debug("==> TagFileSource.createTagObjects()"); - } - - Map<Long, RangerTagDef> tagDefsMap = serviceTags.getTagDefinitions(); - if (MapUtils.isNotEmpty(tagDefsMap)) { - for (Map.Entry<Long, RangerTagDef> entry : tagDefsMap.entrySet()) { - RangerTagDef tagDef = entry.getValue(); - try { - tagSink.createTagDef(tagDef); - } catch (Exception exception) { - // Ignore and continue - LOG.error("createTagDef failed, tagDef=" + tagDef, exception); - } - } - } - - List<RangerServiceResource> serviceResources = serviceTags.getServiceResources(); - if (CollectionUtils.isNotEmpty(serviceResources)) { - for (RangerServiceResource serviceResource : serviceResources) { - try { - tagSink.createServiceResource(serviceResource); - } catch (Exception exception) { - // Ignore and continue - LOG.error("createServiceResource failed, serviceResource=" + serviceResource, exception); - } - } - } - - Map<Long, RangerTag> tagsMap = serviceTags.getTags(); - if (MapUtils.isNotEmpty(tagsMap)) { - for (Map.Entry<Long, RangerTag> entry : tagsMap.entrySet()) { - RangerTag tag = entry.getValue(); - try { - tagSink.createTag(tag); - } catch (Exception exception) { - // Ignore and continue - LOG.error("createTag failed, tag=" + tag, exception); - } - } - } - - Map<Long, List<Long>> resourceTagIdsMap = serviceTags.getResourceToTagIds(); - if (MapUtils.isNotEmpty(resourceTagIdsMap)) { - for (Map.Entry<Long, List<Long>> entry : resourceTagIdsMap.entrySet()) { - Long resourceId = entry.getKey(); - List<Long> tagIds = entry.getValue(); - for (Long tagId : tagIds) { - RangerTagResourceMap tagResourceMap = new RangerTagResourceMap(); - tagResourceMap.setResourceId(resourceId); - tagResourceMap.setTagId(tagId); - try { - tagSink.createTagResourceMap(tagResourceMap); - } catch (Exception exception) { - LOG.error("createTagResourceMap failed, resourceId=" + resourceId + ", tagId=" + tagId, exception); - } - } - } - } - - if (LOG.isDebugEnabled()) { - LOG.debug("<== TagFileSource.createTagObjects()"); - } - } - */ - /* - private void deleteTagObjects(ServiceTags serviceTags) { - if (LOG.isDebugEnabled()) { - LOG.debug("==> TagFileSource.deleteTagObjects()"); - } - - Map<Long, List<Long>> resourceTagIdsMap = serviceTags.getResourceToTagIds(); - if (MapUtils.isNotEmpty(resourceTagIdsMap)) { - for (Map.Entry<Long, List<Long>> entry : resourceTagIdsMap.entrySet()) { - Long resourceId = entry.getKey(); - List<Long> tagIds = entry.getValue(); - for (Long tagId : tagIds) { - try { - tagSink.deleteTagResourceMap(tagId, resourceId); - } catch (Exception exception) { - LOG.error("deleteTagResourceMap failed, resourceId=" + resourceId + ", tagId=" + tagId +")", exception); - } - } - } - } - - List<RangerServiceResource> serviceResources = serviceTags.getServiceResources(); - if (CollectionUtils.isNotEmpty(serviceResources)) { - for (RangerServiceResource serviceResource : serviceResources) { - try { - tagSink.deleteServiceResource(serviceResource.getId()); - } catch (Exception exception) { - // Ignore and continue - LOG.error("deleteServiceResource failed, serviceResource=" + serviceResource, exception); - } - } - } - - Map<Long, RangerTag> tagsMap = serviceTags.getTags(); - if (MapUtils.isNotEmpty(tagsMap)) { - for (Map.Entry<Long, RangerTag> entry : tagsMap.entrySet()) { - Long tagId = entry.getKey(); - try { - tagSink.deleteTag(tagId); - } catch (Exception exception) { - // Ignore and continue - LOG.error("deleteTag failed, tagId=" + tagId, exception); - } - } - } - - Map<Long, RangerTagDef> tagDefsMap = serviceTags.getTagDefinitions(); - if (MapUtils.isNotEmpty(tagDefsMap)) { - for (Map.Entry<Long, RangerTagDef> entry : tagDefsMap.entrySet()) { - Long tagDefId = entry.getKey(); - try { - tagSink.deleteTagDef(tagDefId); - } catch (Exception exception) { - // Ignore and continue - LOG.error("deleteTagDef failed, tagDefId=" + tagDefId, exception); - } - } - } - - if (LOG.isDebugEnabled()) { - LOG.debug("<== TagFileSource.deleteTagObjects()"); - } - } - */ - @Override public boolean isChanged() { @@ -345,26 +202,6 @@ public class TagFileSource implements TagSource, Runnable { return ret; } - @Override - public List<RangerTagDef> fetchAllTagDefs(String syncSentinel) throws Exception { - throw new Exception("Not implemented"); - } - - @Override - public List<RangerTagDef> receiveUpdatesToTagDefs() throws Exception { - throw new Exception("Not implemented"); - } - - @Override - public List<RangerTagResourceMap> fetchAllTaggedEntities() throws Exception { - throw new Exception("Not implemented"); - } - - @Override - public List<RangerTagResourceMap> receiveUpdatesToTaggedEntities() throws Exception { - throw new Exception("Not implemented"); - } - private ServiceTags readFromFile() { if (LOG.isDebugEnabled()) { http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/eb874038/tagsync/src/main/resources/ranger-tagsync-default.xml ---------------------------------------------------------------------- diff --git a/tagsync/src/main/resources/ranger-tagsync-default.xml b/tagsync/src/main/resources/ranger-tagsync-default.xml new file mode 100644 index 0000000..fabe04e --- /dev/null +++ b/tagsync/src/main/resources/ranger-tagsync-default.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + 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. See accompanying LICENSE file. +--> + +<!-- Put site-specific property overrides in this file. --> + +<configuration> + <property> + <name>ranger.tagsync.port</name> + <value>6161</value> + </property> + <property> + <name>ranger.tagsync.ssl</name> + <value>true</value> + </property> + <property> + <name>ranger.tagsync.enabled</name> + <value>true</value> + </property> + <property> + <name>ranger.tagsync.logdir</name> + <value>./log</value> + </property> + <property> + <name>ranger.authentication.method</name> + <value>NONE</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.tagadmin.rest.url</name> + <value>http://localhost:6080</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.tagadmin.rest.ssl.config.file</name> + <value></value> + <description></description> + </property> + <property> + <name>ranger.tagsync.policymanager.basicauth.username</name> + <value>admin</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.policymanager.basicauth.password</name> + <value>admin</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.sleeptimeinmillisbetweensynccycle</name> + <value>60000</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.source.file</name> + <value>/etc/ranger/data/tags.json</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.source.impl.class</name> + <value>org.apache.ranger.source.file.TagFileSource</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.sink.impl.class</name> + <value>org.apache.ranger.sink.policymgr.TagRESTSink</value> + <description></description> + </property> + <property> + <name>atlas.endpoint</name> + <value>http://localhost:21000/</value> + <description></description> + </property> + <property> + <name>ranger.tagsync.atlas.hive.instance.c1.ranger.service</name> + <value>cl1_hive</value> + <description></description> + </property> +</configuration> http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/eb874038/tagsync/src/main/resources/ranger-tagsync-site.xml ---------------------------------------------------------------------- diff --git a/tagsync/src/main/resources/ranger-tagsync-site.xml b/tagsync/src/main/resources/ranger-tagsync-site.xml deleted file mode 100644 index 63b9727..0000000 --- a/tagsync/src/main/resources/ranger-tagsync-site.xml +++ /dev/null @@ -1,67 +0,0 @@ -<!-- - 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. See accompanying LICENSE file. ---> - - -<configuration> - <property> - <name>ranger.authentication.method</name> - <value>NONE</value> - <description></description> - </property> - <property> - <name>ranger.tagsync.policymanager.baseURL</name> - <value>http://ranger-tag-policy-akulkarni-1:6080</value> - <description></description> - </property> - <property> - <name>ranger.tagsync.policymanager.ssl.config.file</name> - <value></value> - <description></description> - </property> - <property> - <name>ranger.tagsync.policymanager.basicauth.username</name> - <value>admin</value> - <description></description> - </property> - <property> - <name>ranger.tagsync.policymanager.basicauth.password</name> - <value>admin</value> - <description></description> - </property> - <property> - <name>ranger.tagsync.sleeptimeinmillisbetweensynccycle</name> - <value>60000</value> - <description></description> - </property> - <property> - <name>ranger.tagsync.source.file</name> - <value>/etc/ranger/data/tags.txt</value> - <description></description> - </property> - <property> - <name>ranger.tagsync.source.impl.class</name> - <value>org.apache.ranger.source.file.TagFileSource</value> - <description></description> - </property> - <property> - <name>ranger.tagsync.sink.impl.class</name> - <value>org.apache.ranger.sink.policymgr.TagRESTSink</value> - <description></description> - </property> - <property> - <name>atlas.endpoint</name> - <value>http://ranger-tag-policy-akulkarni-1:21000/</value> - <description></description> - </property> -</configuration> http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/eb874038/tagsync/src/test/java/org/apache/ranger/process/TestTagSynchronizer.java ---------------------------------------------------------------------- diff --git a/tagsync/src/test/java/org/apache/ranger/process/TestTagSynchronizer.java b/tagsync/src/test/java/org/apache/ranger/process/TestTagSynchronizer.java index d810048..e693696 100644 --- a/tagsync/src/test/java/org/apache/ranger/process/TestTagSynchronizer.java +++ b/tagsync/src/test/java/org/apache/ranger/process/TestTagSynchronizer.java @@ -21,7 +21,7 @@ package org.apache.ranger.process; import org.apache.ranger.model.TagSource; -//import org.apache.ranger.source.atlas.TagAtlasSource; +import org.apache.ranger.source.atlas.TagAtlasSource; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -42,14 +42,9 @@ public class TestTagSynchronizer { TagSyncConfig config = TagSyncConfig.getInstance(); - Properties props = config.getProperties(); - - System.out.println("Tester is configured with following properties--"); - System.out.println("--------------------"); + TagSyncConfig.dumpConfiguration(config, new BufferedWriter(new OutputStreamWriter(System.out))); - config.toString(); - - System.out.println("--------------------"); + Properties props = config.getProperties(); tagSynchronizer = new TagSynchronizer(props); @@ -78,18 +73,20 @@ public class TestTagSynchronizer { @Test public void testTagDownload() { - boolean initDone = tagSynchronizer.init(); + boolean initDone = tagSynchronizer.initLoop(); System.out.println("TagSynchronizer initialization result=" + initDone); + /* TagSource tagSource = tagSynchronizer.getTagSource(); try { - //TagAtlasSource tagAtlasSource = (TagAtlasSource) tagSource; + TagAtlasSource tagAtlasSource = (TagAtlasSource) tagSource; //tagAtlasSource.printAllEntities(); } catch (ClassCastException exception) { System.err.println("TagSource is not of TagAtlasSource"); } + */ System.out.println("Exiting testTagDownload()"); }
