/** * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.atlas.catalog; import org.apache.atlas.catalog.definition.TermResourceDefinition; import org.apache.atlas.catalog.exception.*; import org.apache.atlas.catalog.query.AtlasQuery; import java.util.*; /** * Provider for Term resources. */ public class TermResourceProvider extends BaseResourceProvider implements ResourceProvider { private ResourceProvider taxonomyResourceProvider; private ResourceProvider entityResourceProvider; private ResourceProvider entityTagResourceProvider; public TermResourceProvider(AtlasTypeSystem typeSystem) { super(typeSystem, new TermResourceDefinition()); } @Override public Result getResourceById(Request request) throws ResourceNotFoundException { //todo: shouldn't need to add this here request.getQueryProperties().put("name", request.<TermPath>getProperty("termPath").getFullyQualifiedName()); AtlasQuery atlasQuery; try { atlasQuery = queryFactory.createTermQuery(request); } catch (InvalidQueryException e) { throw new CatalogRuntimeException("Unable to compile internal Term query: " + e, e); } Collection<Map<String, Object>> results = atlasQuery.execute(); if (results.isEmpty()) { throw new ResourceNotFoundException(String.format("Term '%s' not found.", request.<TermPath>getProperty("termPath").getFullyQualifiedName())); } return new Result(results); } public Result getResources(Request request) throws InvalidQueryException, ResourceNotFoundException { TermPath termPath = request.getProperty("termPath"); String queryString = doQueryStringConversions(termPath, request.getQueryString()); Request queryRequest = new CollectionRequest(request.getQueryProperties(), queryString); AtlasQuery atlasQuery = queryFactory.createTermQuery(queryRequest); Collection<Map<String, Object>> result = atlasQuery.execute(); return new Result(result); } public void createResource(Request request) throws InvalidPayloadException, ResourceAlreadyExistsException, ResourceNotFoundException { TermPath termPath = (TermPath) request.getQueryProperties().remove("termPath"); String qualifiedTermName = termPath.getFullyQualifiedName(); request.getQueryProperties().put("name", qualifiedTermName); resourceDefinition.validateCreatePayload(request); // get taxonomy Request taxonomyRequest = new InstanceRequest( Collections.<String, Object>singletonMap("name", termPath.getTaxonomyName())); taxonomyRequest.addAdditionalSelectProperties(Collections.singleton("id")); Result taxonomyResult = getTaxonomyResourceProvider().getResourceById(taxonomyRequest); Map<String, Object> taxonomyPropertyMap = taxonomyResult.getPropertyMaps().iterator().next(); // ensure that parent exists if not a root level term if (! termPath.getPath().equals("/")) { Map<String, Object> parentProperties = new HashMap<>(request.getQueryProperties()); parentProperties.put("termPath", termPath.getParent()); getResourceById(new InstanceRequest(parentProperties)); } typeSystem.createTraitType(resourceDefinition, qualifiedTermName, request.<String>getProperty("description")); typeSystem.createTraitInstance(String.valueOf(taxonomyPropertyMap.get("id")), qualifiedTermName, request.getQueryProperties()); } @Override public Collection<String> createResources(Request request) throws InvalidQueryException, ResourceNotFoundException { throw new UnsupportedOperationException("Creating multiple Terms in a request is not currently supported"); } @Override public void updateResourceById(Request request) throws ResourceNotFoundException, InvalidPayloadException { resourceDefinition.validateUpdatePayload(request); String termName = request.<TermPath>getProperty("termPath").getFullyQualifiedName(); request.getQueryProperties().put("name", termName); AtlasQuery atlasQuery; try { atlasQuery = queryFactory.createTermQuery(request); } catch (InvalidQueryException e) { throw new CatalogRuntimeException("Unable to compile internal Term query: " + e, e); } Map<String, Object> updateProperties = request.getUpdateProperties(); Collection<Map<String, Object>> results = atlasQuery.execute(updateProperties); if (results.isEmpty()) { throw new ResourceNotFoundException(String.format("Term '%s' not found.", termName)); } // only the term 'description' property is set on entity tags if (updateProperties.containsKey("description")) { // 'description' property is being updated so we need to update tags String tagQueryString = String.format("name:%s", termName); Request tagRequest = new CollectionRequest( Collections.<String, Object>singletonMap("id", "*"), tagQueryString, null); AtlasQuery tagQuery; try { tagQuery = queryFactory.createEntityTagQuery(tagRequest); } catch (InvalidQueryException e) { throw new CatalogRuntimeException("Unable to compile internal Entity Tag query: " + e, e); } tagQuery.execute(Collections.singletonMap("description", updateProperties.get("description"))); } } @Override public void deleteResourceById(Request request) throws ResourceNotFoundException, InvalidPayloadException { // will result in expected ResourceNotFoundException if term doesn't exist getResourceById(request); TermPath termPath = (TermPath) request.getQueryProperties().get("termPath"); String taxonomyId = getTaxonomyId(termPath); deleteChildren(taxonomyId, termPath); deleteTerm(taxonomyId, termPath); } protected void deleteChildren(String taxonomyId, TermPath termPath) throws ResourceNotFoundException, InvalidPayloadException { TermPath collectionTermPath = new TermPath(termPath.getFullyQualifiedName() + "."); Request queryRequest = new CollectionRequest(Collections.<String, Object>singletonMap("termPath", collectionTermPath), null); AtlasQuery collectionQuery; try { collectionQuery = queryFactory.createTermQuery(queryRequest); } catch (InvalidQueryException e) { throw new CatalogRuntimeException("Failed to compile internal predicate: " + e, e); } Collection<Map<String, Object>> children = collectionQuery.execute(); for (Map<String, Object> childMap : children) { deleteTerm(taxonomyId, new TermPath(String.valueOf(childMap.get("name")))); } } private void deleteTerm(String taxonomyId, TermPath termPath) throws ResourceNotFoundException, InvalidPayloadException { String fullyQualifiedName = termPath.getFullyQualifiedName(); deleteEntityTagsForTerm(fullyQualifiedName); // delete term instance associated with the taxonomy typeSystem.deleteTag(taxonomyId, fullyQualifiedName); //todo: Currently no way to delete type via MetadataService or MetadataRepository } private void deleteEntityTagsForTerm(String fullyQualifiedName) throws ResourceNotFoundException { String entityQueryStr = String.format("tags/name:%s", fullyQualifiedName); Request entityRequest = new CollectionRequest(Collections.<String, Object>emptyMap(), entityQueryStr); Result entityResult; try { entityResult = getEntityResourceProvider().getResources(entityRequest); } catch (InvalidQueryException e) { throw new CatalogRuntimeException(String.format( "Failed to compile internal predicate for query '%s': %s", entityQueryStr, e), e); } for (Map<String, Object> entityResultMap : entityResult.getPropertyMaps()) { Map<String, Object> tagRequestProperties = new HashMap<>(); tagRequestProperties.put("id", String.valueOf(entityResultMap.get("id"))); tagRequestProperties.put("name", fullyQualifiedName); try { getEntityTagResourceProvider().deleteResourceById(new InstanceRequest(tagRequestProperties)); } catch (InvalidPayloadException e) { throw new CatalogRuntimeException( "An internal error occurred while trying to delete an entity tag: " + e, e); } } } private String getTaxonomyId(TermPath termPath) throws ResourceNotFoundException { Request taxonomyRequest = new InstanceRequest(Collections.<String, Object>singletonMap( "name", termPath.getTaxonomyName())); taxonomyRequest.addAdditionalSelectProperties(Collections.singleton("id")); // will result in proper ResourceNotFoundException if taxonomy doesn't exist Result taxonomyResult = getTaxonomyResourceProvider().getResourceById(taxonomyRequest); Map<String, Object> taxonomyResultMap = taxonomyResult.getPropertyMaps().iterator().next(); return String.valueOf(taxonomyResultMap.get("id")); } //todo: add generic support for pre-query modification of expected value //todo: similar path parsing code is used in several places in this class private String doQueryStringConversions(TermPath termPath, String queryStr) throws InvalidQueryException { String hierarchyPathProp = "hierarchy/path"; // replace "." if (queryStr != null && queryStr.contains(String.format("%s:.", hierarchyPathProp))) { //todo: regular expression replacement queryStr = queryStr.replaceAll(String.format("%s:.", hierarchyPathProp), String.format("%s:%s", hierarchyPathProp, termPath.getPath())); } return queryStr; } protected synchronized ResourceProvider getTaxonomyResourceProvider() { if (taxonomyResourceProvider == null) { taxonomyResourceProvider = new TaxonomyResourceProvider(typeSystem); } return taxonomyResourceProvider; } protected synchronized ResourceProvider getEntityResourceProvider() { if (entityResourceProvider == null) { entityResourceProvider = new EntityResourceProvider(typeSystem); } return entityResourceProvider; } protected synchronized ResourceProvider getEntityTagResourceProvider() { if (entityTagResourceProvider == null) { entityTagResourceProvider = new EntityTagResourceProvider(typeSystem); } return entityTagResourceProvider; } }