Repository: incubator-atlas Updated Branches: refs/heads/master b65dd91c3 -> aaf2971ad
http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/test/java/org/apache/atlas/catalog/query/AlwaysQueryExpressionTest.java ---------------------------------------------------------------------- diff --git a/catalog/src/test/java/org/apache/atlas/catalog/query/AlwaysQueryExpressionTest.java b/catalog/src/test/java/org/apache/atlas/catalog/query/AlwaysQueryExpressionTest.java new file mode 100644 index 0000000..81d70a4 --- /dev/null +++ b/catalog/src/test/java/org/apache/atlas/catalog/query/AlwaysQueryExpressionTest.java @@ -0,0 +1,74 @@ +/** + * 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.query; + +import org.apache.atlas.catalog.VertexWrapper; +import org.testng.annotations.Test; + +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +/** + * Unit tests for AlwaysQueryExpression. + */ +public class AlwaysQueryExpressionTest { + @Test + public void testEvaluate() { + VertexWrapper v = createStrictMock(VertexWrapper.class); + replay(v); + QueryExpression expression = new AlwaysQueryExpression(); + // always returns true + assertTrue(expression.evaluate(v)); + verify(v); + } + + @Test + public void testEvaluate_negated() { + VertexWrapper v = createStrictMock(VertexWrapper.class); + replay(v); + QueryExpression expression = new AlwaysQueryExpression(); + expression.setNegate(); + // always returns true + assertFalse(expression.evaluate(v)); + assertTrue(expression.isNegate()); + verify(v); + } + + @Test + public void testGetProperties() { + VertexWrapper v = createStrictMock(VertexWrapper.class); + replay(v); + QueryExpression expression = new AlwaysQueryExpression(); + assertTrue(expression.getProperties().isEmpty()); + verify(v); + } + + @Test + public void testAsPipe() { + VertexWrapper v = createStrictMock(VertexWrapper.class); + replay(v); + QueryExpression expression = new AlwaysQueryExpression(); + assertNull(expression.asPipe()); + verify(v); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/test/java/org/apache/atlas/catalog/query/QueryFactoryTest.java ---------------------------------------------------------------------- diff --git a/catalog/src/test/java/org/apache/atlas/catalog/query/QueryFactoryTest.java b/catalog/src/test/java/org/apache/atlas/catalog/query/QueryFactoryTest.java new file mode 100644 index 0000000..36cb6dc --- /dev/null +++ b/catalog/src/test/java/org/apache/atlas/catalog/query/QueryFactoryTest.java @@ -0,0 +1,209 @@ +/** + * 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.query; + +import org.apache.atlas.catalog.CollectionRequest; +import org.apache.atlas.catalog.InstanceRequest; +import org.apache.atlas.catalog.Request; +import org.apache.atlas.catalog.TermPath; +import org.apache.atlas.catalog.definition.EntityResourceDefinition; +import org.apache.atlas.catalog.definition.EntityTagResourceDefinition; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.testng.Assert.assertEquals; + +/** + * Unit tests for QueryFactory. + */ +public class QueryFactoryTest { + @Test + public void testCreateTaxonomyQuery() throws Exception { + Map<String, Object> requestProps = new HashMap<>(); + requestProps.put("name", "test_taxonomy"); + Request request = new InstanceRequest(requestProps); + + QueryFactory factory = new QueryFactory(); + AtlasTaxonomyQuery query = (AtlasTaxonomyQuery) factory.createTaxonomyQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), TermQueryExpression.class); + assertEquals(queryExpression.getField(), "name"); + assertEquals(queryExpression.getExpectedValue(), "test_taxonomy"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Taxonomy"); + } + + @Test + public void testCreateTermQuery() throws Exception { + Map<String, Object> requestProps = new HashMap<>(); + requestProps.put("name", "test_taxonomy.term1"); + requestProps.put("termPath", new TermPath("test_taxonomy.term1")); + Request request = new InstanceRequest(requestProps); + + + + QueryFactory factory = new QueryFactory(); + AtlasTermQuery query = (AtlasTermQuery) factory.createTermQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), TermQueryExpression.class); + assertEquals(queryExpression.getField(), "name"); + assertEquals(queryExpression.getExpectedValue(), "test_taxonomy.term1"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Term"); + } + + @Test + public void testCreateEntityQuery() throws Exception { + Map<String, Object> requestProps = new HashMap<>(); + requestProps.put("id", "foo"); + Request request = new InstanceRequest(requestProps); + + QueryFactory factory = new QueryFactory(); + AtlasEntityQuery query = (AtlasEntityQuery) factory.createEntityQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), TermQueryExpression.class); + assertEquals(queryExpression.getField(), "id"); + assertEquals(queryExpression.getExpectedValue(), "foo"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getClass(), EntityResourceDefinition.class); + } + + @Test + public void testCreateEntityTagQuery() throws Exception { + Map<String, Object> requestProps = new HashMap<>(); + requestProps.put("id", "entity_id"); + requestProps.put("name", "test_taxonomy.term1"); + Request request = new InstanceRequest(requestProps); + + QueryFactory factory = new QueryFactory(); + AtlasEntityTagQuery query = (AtlasEntityTagQuery) factory.createEntityTagQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), TermQueryExpression.class); + assertEquals(queryExpression.getField(), "name"); + assertEquals(queryExpression.getExpectedValue(), "test_taxonomy.term1"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getClass(), EntityTagResourceDefinition.class); + } + + @Test + public void testCollectionQuery_TermQuery() throws Exception { + String queryString = "name:test_taxonomy"; + Request request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + + QueryFactory factory = new QueryFactory(); + AtlasTaxonomyQuery query = (AtlasTaxonomyQuery) factory.createTaxonomyQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), TermQueryExpression.class); + assertEquals(queryExpression.getField(), "name"); + assertEquals(queryExpression.getExpectedValue(), "test_taxonomy"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Taxonomy"); + } + + @Test + public void testCollectionQuery_PrefixQuery() throws Exception { + String queryString = "name:t*"; + Request request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + + QueryFactory factory = new QueryFactory(); + AtlasTaxonomyQuery query = (AtlasTaxonomyQuery) factory.createTaxonomyQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), PrefixQueryExpression.class); + assertEquals(queryExpression.getField(), "name"); + assertEquals(queryExpression.getExpectedValue(), "t"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Taxonomy"); + } + + @Test + public void testCollectionQuery_TermRangeQuery() throws Exception { + String queryString = "creation_time:[2013-01-01:07:29:00 TO 2017-01-02]"; + Request request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + + QueryFactory factory = new QueryFactory(); + AtlasTaxonomyQuery query = (AtlasTaxonomyQuery) factory.createTaxonomyQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), TermRangeQueryExpression.class); + assertEquals(queryExpression.getField(), "creation_time"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Taxonomy"); + } + + @Test + public void testCollectionQuery_WildcardQuery() throws Exception { + String queryString = "name:ta?onomy"; + Request request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + + QueryFactory factory = new QueryFactory(); + AtlasTaxonomyQuery query = (AtlasTaxonomyQuery) factory.createTaxonomyQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), WildcardQueryExpression.class); + assertEquals(queryExpression.getField(), "name"); + assertEquals(queryExpression.getExpectedValue(), "ta?onomy"); + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Taxonomy"); + } + + @Test + public void testCollectionQuery_BooleanQuery() throws Exception { + String queryString = "name:foo OR name:bar"; + Request request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + + QueryFactory factory = new QueryFactory(); + AtlasTaxonomyQuery query = (AtlasTaxonomyQuery) factory.createTaxonomyQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), BooleanQueryExpression.class); + + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Taxonomy"); + } + + @Test + public void testCollectionQuery_ProjectionQuery() throws Exception { + String queryString = "relation/name:foo"; + Request request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + + QueryFactory factory = new QueryFactory(); + AtlasTaxonomyQuery query = (AtlasTaxonomyQuery) factory.createTaxonomyQuery(request); + + QueryExpression queryExpression = query.getQueryExpression(); + assertEquals(queryExpression.getClass(), ProjectionQueryExpression.class); + + ProjectionQueryExpression projectionExpression = (ProjectionQueryExpression) queryExpression; + QueryExpression underlyingExpression = projectionExpression.getUnderlyingExpression(); + assertEquals(underlyingExpression.getClass(), TermQueryExpression.class); + assertEquals(underlyingExpression.getField(), QueryFactory.escape("relation/name")); + assertEquals(underlyingExpression.getExpectedValue(), "foo"); + + assertEquals(query.getRequest(), request); + assertEquals(query.getResourceDefinition().getTypeName(), "Taxonomy"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/distro/src/conf/policy-store.txt ---------------------------------------------------------------------- diff --git a/distro/src/conf/policy-store.txt b/distro/src/conf/policy-store.txt index b072b49..5bcc1cf 100644 --- a/distro/src/conf/policy-store.txt +++ b/distro/src/conf/policy-store.txt @@ -2,8 +2,8 @@ ##r-READ, w-WRITE, u-UPDATE, d-DELETE ##Policy_Name;;User_Name1:Operations_Allowed,User_Name2:Operations_Allowed;;Group_Name1:Operations_Allowed,Group_Name2:Operations_Allowed;;Resource_Type1:Resource_Name,Resource_Type2:Resource_Name ## -adminPolicy;;admin:rwud;;ROLE_ADMIN:rwud;;type:*,entity:*,operation:* -typeReadPolicy;;nixon:rw;;;;type:*,entity:* -classReadPolicy;;saqeeb:r;;;;type:*,entity:* -dataScientistPolicy;;;;DATA_SCIENTIST:r;;type:*,entity:* -dataStewardPolicy;;;;DATA_STEWARD:rwu;;type:*,entity:* +adminPolicy;;admin:rwud;;ROLE_ADMIN:rwud;;type:*,entity:*,operation:*,taxonomy:*,term:* +typeReadPolicy;;nixon:rw;;;;type:*,entity:*,taxonomy:*,term:* +classReadPolicy;;saqeeb:r;;;;type:*,entity:*,taxonomy:*,term:* +dataScientistPolicy;;;;DATA_SCIENTIST:r;;type:*,entity:*,taxonomy:*,term:* +dataStewardPolicy;;;;DATA_STEWARD:rwu;;type:*,entity:*,taxonomy:*,term:* http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 5e2871e..30fb95a 100755 --- a/pom.xml +++ b/pom.xml @@ -463,6 +463,7 @@ <module>graphdb</module> <module>titan</module> <module>repository</module> + <module>catalog</module> <!-- <module>dashboard</module> --> <module>dashboardv2</module> <module>webapp</module> @@ -1087,6 +1088,12 @@ <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.atlas</groupId> + <artifactId>atlas-catalog</artifactId> + <version>${project.version}</version> + </dependency> + <!--Scala dependencies--> <dependency> <groupId>org.scala-lang</groupId> @@ -1238,7 +1245,7 @@ <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> - <version>2.4</version> + <version>3.4</version> <scope>test</scope> </dependency> http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/release-log.txt ---------------------------------------------------------------------- diff --git a/release-log.txt b/release-log.txt index a68010a..d7e182d 100644 --- a/release-log.txt +++ b/release-log.txt @@ -21,6 +21,7 @@ ATLAS-409 Atlas will not import avro tables with schema read from a file (dosset ATLAS-379 Create sqoop and falcon metadata addons (venkatnrangan,bvellanki,sowmyaramesh via shwethags) ALL CHANGES: +ATLAS-491 Business Catalog / Taxonomy (jspeidel via yhemanth) ATLAS-713 Entity lineage based on entity id (shwethags) ATLAS-736 UI - BUG :: displaying timestamp values for hive_db description (kevalbhatt18 via yhemanth) ATLAS-784 Configure config.store.uri for Falcon hook IT (yhemanth) http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java b/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java index 5195cbe..7cd83f8 100755 --- a/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java +++ b/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java @@ -313,10 +313,14 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang ITypedReferenceableInstance[] typedInstances = deserializeClassInstances(entityInstanceDefinition); - final List<String> guids = repository.createEntities(typedInstances); + List<String> guids = createEntities(typedInstances); + return new JSONArray(guids).toString(); + } + public List<String> createEntities(ITypedReferenceableInstance[] typedInstances) throws AtlasException { + final List<String> guids = repository.createEntities(typedInstances); onEntitiesAdded(guids); - return new JSONArray(guids).toString(); + return guids; } private ITypedReferenceableInstance[] deserializeClassInstances(String entityInstanceDefinition) @@ -327,18 +331,7 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang for (int index = 0; index < referableInstances.length(); index++) { Referenceable entityInstance = InstanceSerialization.fromJsonReferenceable(referableInstances.getString(index), true); - final String entityTypeName = entityInstance.getTypeName(); - ParamChecker.notEmpty(entityTypeName, "Entity type cannot be null"); - - ClassType entityType = typeSystem.getDataType(ClassType.class, entityTypeName); - - //Both assigned id and values are required for full update - //classtype.convert() will remove values if id is assigned. So, set temp id, convert and - // then replace with original id - Id origId = entityInstance.getId(); - entityInstance.replaceWithNewId(new Id(entityInstance.getTypeName())); - ITypedReferenceableInstance typedInstrance = entityType.convert(entityInstance, Multiplicity.REQUIRED); - ((ReferenceableInstance)typedInstrance).replaceWithNewId(origId); + ITypedReferenceableInstance typedInstrance = getTypedReferenceableInstance(entityInstance); instances[index] = typedInstrance; } return instances; @@ -350,6 +343,23 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang } } + @Override + public ITypedReferenceableInstance getTypedReferenceableInstance(Referenceable entityInstance) throws AtlasException { + final String entityTypeName = entityInstance.getTypeName(); + ParamChecker.notEmpty(entityTypeName, "Entity type cannot be null"); + + ClassType entityType = typeSystem.getDataType(ClassType.class, entityTypeName); + + //Both assigned id and values are required for full update + //classtype.convert() will remove values if id is assigned. So, set temp id, convert and + // then replace with original id + Id origId = entityInstance.getId(); + entityInstance.replaceWithNewId(new Id(entityInstance.getTypeName())); + ITypedReferenceableInstance typedInstrance = entityType.convert(entityInstance, Multiplicity.REQUIRED); + ((ReferenceableInstance)typedInstrance).replaceWithNewId(origId); + return typedInstrance; + } + /** * Return the definition for the given guid. * @@ -579,6 +589,10 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang ParamChecker.notEmpty(traitInstanceDefinition, "trait instance definition"); ITypedStruct traitInstance = deserializeTraitInstance(traitInstanceDefinition); + addTrait(guid, traitInstance); + } + + public void addTrait(String guid, ITypedStruct traitInstance) throws AtlasException { final String traitName = traitInstance.getTypeName(); // ensure trait type is already registered with the TS @@ -591,7 +605,7 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang // ensure trait is not already defined Preconditions .checkArgument(!getTraitNames(guid).contains(traitName), "trait=%s is already defined for entity=%s", - traitName, guid); + traitName, guid); repository.addTrait(guid, traitInstance); @@ -601,8 +615,12 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang private ITypedStruct deserializeTraitInstance(String traitInstanceDefinition) throws AtlasException { + return createTraitInstance(InstanceSerialization.fromJsonStruct(traitInstanceDefinition, true)); + } + + @Override + public ITypedStruct createTraitInstance(Struct traitInstance) throws AtlasException { try { - Struct traitInstance = InstanceSerialization.fromJsonStruct(traitInstanceDefinition, true); final String entityTypeName = traitInstance.getTypeName(); ParamChecker.notEmpty(entityTypeName, "entity type"); http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/server-api/src/main/java/org/apache/atlas/services/MetadataService.java ---------------------------------------------------------------------- diff --git a/server-api/src/main/java/org/apache/atlas/services/MetadataService.java b/server-api/src/main/java/org/apache/atlas/services/MetadataService.java index 13d20d8..c8c1067 100644 --- a/server-api/src/main/java/org/apache/atlas/services/MetadataService.java +++ b/server-api/src/main/java/org/apache/atlas/services/MetadataService.java @@ -21,7 +21,10 @@ package org.apache.atlas.services; import org.apache.atlas.AtlasException; import org.apache.atlas.EntityAuditEvent; import org.apache.atlas.listener.EntityChangeListener; +import org.apache.atlas.typesystem.ITypedReferenceableInstance; +import org.apache.atlas.typesystem.ITypedStruct; import org.apache.atlas.typesystem.Referenceable; +import org.apache.atlas.typesystem.Struct; import org.apache.atlas.typesystem.types.DataTypes; import org.codehaus.jettison.json.JSONObject; @@ -80,6 +83,26 @@ public interface MetadataService { String createEntities(String entityDefinition) throws AtlasException; /** + * Get a typed entity instance. + * + * @param entity entity + * @return typed entity instance + * + * @throws AtlasException if any failure occurs + */ + ITypedReferenceableInstance getTypedReferenceableInstance(Referenceable entity) throws AtlasException; + + /** + * Create entity instances. + * + * @param typedInstances instance to create + * @return collection of guids for created entities + * + * @throws AtlasException if unable to create the entities + */ + List<String> createEntities(ITypedReferenceableInstance[] typedInstances) throws AtlasException; + + /** * Return the definition for the given guid. * * @param guid guid @@ -166,6 +189,11 @@ public interface MetadataService { */ void addTrait(String guid, String traitInstanceDefinition) throws AtlasException; + //todo: + void addTrait(String guid, ITypedStruct traitInstance) throws AtlasException; + ITypedStruct createTraitInstance(Struct traitInstance) throws AtlasException; + + /** * Deletes a given trait from an existing entity represented by a guid. * http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/pom.xml ---------------------------------------------------------------------- diff --git a/webapp/pom.xml b/webapp/pom.xml index de48c15..4b67ffa 100755 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -108,6 +108,11 @@ <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> </dependency> + + <dependency> + <groupId>org.apache.atlas</groupId> + <artifactId>atlas-catalog</artifactId> + </dependency> <!-- supports simple auth handler --> <dependency> http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/java/org/apache/atlas/authorize/AtlasAuthorizationUtils.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/authorize/AtlasAuthorizationUtils.java b/webapp/src/main/java/org/apache/atlas/authorize/AtlasAuthorizationUtils.java index 211ee7f..14a2aac 100644 --- a/webapp/src/main/java/org/apache/atlas/authorize/AtlasAuthorizationUtils.java +++ b/webapp/src/main/java/org/apache/atlas/authorize/AtlasAuthorizationUtils.java @@ -19,8 +19,6 @@ package org.apache.atlas.authorize; import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; import java.util.List; import javax.servlet.ServletException; @@ -55,9 +53,13 @@ public class AtlasAuthorizationUtils { } if (u.startsWith(BASE_URL)) { u = parse(u, BASE_URL); + } else { + // strip of leading '/' + u = u.substring(1); } String[] split = u.split("/"); - return split[0]; + String api = split[0]; + return (! api.equals("v1")) ? api : String.format("v1/%s", split[1]); } public static AtlasActionTypes getAtlasAction(String method) { @@ -99,7 +101,7 @@ public class AtlasAuthorizationUtils { if (api.startsWith("types")) { resourceTypes.add(AtlasResourceTypes.TYPE); - } else if ((api.startsWith("discovery") && api.contains("gremlin")) || api.startsWith("admin") + } else if ((api.startsWith("discovery") && contextPath.contains("gremlin")) || api.startsWith("admin") || api.startsWith("graph")) { resourceTypes.add(AtlasResourceTypes.OPERATION); } else if ((api.startsWith("entities") && contextPath.contains("traits")) || api.startsWith("discovery")) { @@ -107,6 +109,21 @@ public class AtlasAuthorizationUtils { resourceTypes.add(AtlasResourceTypes.TYPE); } else if (api.startsWith("entities") || api.startsWith("lineage")) { resourceTypes.add(AtlasResourceTypes.ENTITY); + } else if (api.startsWith("v1/taxonomies")) { + resourceTypes.add(AtlasResourceTypes.TAXONOMY); + // taxonomies are modeled as entities + resourceTypes.add(AtlasResourceTypes.ENTITY); + if (contextPath.contains("terms")) { + resourceTypes.add(AtlasResourceTypes.TERM); + // terms are modeled as traits + resourceTypes.add(AtlasResourceTypes.TYPE); + } + } else if (api.startsWith("v1/entities")) { + resourceTypes.add(AtlasResourceTypes.ENTITY); + if (contextPath.contains("tags")) { + // tags are modeled as traits + resourceTypes.add(AtlasResourceTypes.TYPE); + } } else { LOG.error("Unable to find Atlas Resource corresponding to : " + api); throw new ServletException("Unable to find Atlas Resource corresponding to : " + api); http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/java/org/apache/atlas/authorize/AtlasResourceTypes.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/authorize/AtlasResourceTypes.java b/webapp/src/main/java/org/apache/atlas/authorize/AtlasResourceTypes.java index 8ce3f4c..14a72f1 100644 --- a/webapp/src/main/java/org/apache/atlas/authorize/AtlasResourceTypes.java +++ b/webapp/src/main/java/org/apache/atlas/authorize/AtlasResourceTypes.java @@ -19,5 +19,5 @@ package org.apache.atlas.authorize; public enum AtlasResourceTypes { - ENTITY, TYPE, OPERATION; + ENTITY, TYPE, OPERATION, TAXONOMY, TERM; } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/java/org/apache/atlas/web/resources/BaseService.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/BaseService.java b/webapp/src/main/java/org/apache/atlas/web/resources/BaseService.java new file mode 100644 index 0000000..3982df8 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/resources/BaseService.java @@ -0,0 +1,126 @@ +/** + * 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.web.resources; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import org.apache.atlas.catalog.*; +import org.apache.atlas.catalog.exception.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Collection; +import java.util.Map; + +/** + * Base class for all v1 API services. + */ +public abstract class BaseService { + private static final Gson gson = new Gson(); + private final Logger LOG = LoggerFactory.getLogger(getClass()); + + protected Result getResource(ResourceProvider provider, Request request) + throws ResourceNotFoundException { + + try { + return provider.getResourceById(request); + } catch (RuntimeException e) { + throw wrapRuntimeException(e); + } + } + + protected Result getResources(ResourceProvider provider, Request request) + throws ResourceNotFoundException, InvalidQueryException { + + try { + return provider.getResources(request); + } catch (RuntimeException e) { + throw wrapRuntimeException(e); + } + } + + protected void createResource(ResourceProvider provider, Request request) throws CatalogException { + try { + provider.createResource(request); + } catch (RuntimeException e) { + throw wrapRuntimeException(e); + } + } + + protected Collection<String> createResources(ResourceProvider provider, Request request) throws CatalogException { + + try { + return provider.createResources(request); + } catch (RuntimeException e) { + throw wrapRuntimeException(e); + } + } + + protected String getQueryString(@Context UriInfo ui) { + String uri = ui.getRequestUri().toASCIIString(); + int qsBegin = uri.indexOf("?"); + return (qsBegin == -1) ? null : uri.substring(qsBegin + 1); + } + + protected <T extends Map> T parsePayload(String body) throws InvalidPayloadException { + T properties; + + try { + properties = gson.<T>fromJson(body, Map.class); + } catch (JsonSyntaxException e) { + LOG.info("Unable to parse json in request body", e); + throw new InvalidPayloadException("Request payload contains invalid JSON: " + e.getMessage()); + } + + return properties; + } + + private RuntimeException wrapRuntimeException(RuntimeException e) { + return e instanceof CatalogRuntimeException ? e : new CatalogRuntimeException(e); + } + + protected String decode(String s) throws CatalogException { + try { + return s == null ? null : URLDecoder.decode(s, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new CatalogException("Unable to decode URL: " + e.getMessage(), 500); + } + } + + @XmlRootElement + // the name of this class is used as the collection name in the returned json when returning a collection + public static class Results { + public String href; + public int status; + + public Results() { + // required by JAXB + } + + public Results(String href, int status) { + this.href = href; + this.status = status; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/java/org/apache/atlas/web/resources/CatalogExceptionMapper.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/CatalogExceptionMapper.java b/webapp/src/main/java/org/apache/atlas/web/resources/CatalogExceptionMapper.java new file mode 100644 index 0000000..4f7da2e --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/resources/CatalogExceptionMapper.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.atlas.web.resources; + +import org.apache.atlas.catalog.exception.CatalogException; +import org.apache.atlas.web.util.Servlets; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Exception mapper for CatalogException. + */ +@Provider +public class CatalogExceptionMapper implements ExceptionMapper<CatalogException> { + @Override + public Response toResponse(CatalogException e) { + return Response.status(e.getStatus()).entity( + new ErrorBean(e)).type(Servlets.JSON_MEDIA_TYPE).build(); + } + + @XmlRootElement + public static class ErrorBean { + public int status; + public String message; + + public ErrorBean() { + // required for JAXB + } + + public ErrorBean(CatalogException ex) { + this.status = ex.getStatus(); + this.message = ex.getMessage(); + } + + public int getStatus() { + return status; + } + + public String getMessage() { + return message; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/java/org/apache/atlas/web/resources/CatalogRuntimeExceptionMapper.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/CatalogRuntimeExceptionMapper.java b/webapp/src/main/java/org/apache/atlas/web/resources/CatalogRuntimeExceptionMapper.java new file mode 100644 index 0000000..e11d6d1 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/resources/CatalogRuntimeExceptionMapper.java @@ -0,0 +1,78 @@ +/** + * 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.atlas.web.resources; + +import org.apache.atlas.catalog.exception.CatalogRuntimeException; +import org.apache.atlas.web.util.Servlets; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Exception mapper for CatalogRuntimeException + */ +@Provider +public class CatalogRuntimeExceptionMapper implements ExceptionMapper<CatalogRuntimeException> { + @Override + public Response toResponse(CatalogRuntimeException e) { + return Response.status(e.getStatusCode()).entity( + new ErrorBean(e)).type(Servlets.JSON_MEDIA_TYPE).build(); + } + + @XmlRootElement + public static class ErrorBean { + private static final String MSG_PREFIX = "An unexpected error has occurred. "; + public int status; + public String message; + public String stackTrace; + //todo: error code, developerMsg ... + + public ErrorBean() { + // required for JAXB + } + + public ErrorBean(CatalogRuntimeException ex) { + this.status = 500; + this.message = String.format("%s%s : %s", MSG_PREFIX, ex.toString(), ex.getCause().toString()); + this.stackTrace = getStackTraceFromException(ex); + } + + public int getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public String getStackTrace() { + return stackTrace; + } + + private String getStackTraceFromException(RuntimeException e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/java/org/apache/atlas/web/resources/EntityService.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/EntityService.java b/webapp/src/main/java/org/apache/atlas/web/resources/EntityService.java new file mode 100644 index 0000000..0e3f4c4 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/resources/EntityService.java @@ -0,0 +1,147 @@ +/** + * 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.web.resources; + +import org.apache.atlas.catalog.*; +import org.apache.atlas.catalog.exception.CatalogException; +import org.apache.atlas.services.MetadataService; +import org.apache.atlas.web.util.Servlets; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import java.util.*; + +/** + * Service which handles API requests for v1 entity resources. + */ +@Path("v1/entities") +@Singleton +public class EntityService extends BaseService { + + private final EntityResourceProvider entityResourceProvider; + private final EntityTagResourceProvider entityTagResourceProvider; + private static JsonSerializer m_serializer = new JsonSerializer(); + + @Inject + public EntityService(MetadataService metadataService) { + DefaultTypeSystem typeSystem = new DefaultTypeSystem(metadataService); + entityResourceProvider = new EntityResourceProvider(typeSystem); + entityTagResourceProvider = new EntityTagResourceProvider(typeSystem); + } + + @GET + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getEntities(@Context HttpHeaders headers, @Context UriInfo ui) throws CatalogException { + String queryString = decode(getQueryString(ui)); + + BaseRequest request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + Result result = getResources(entityResourceProvider, request); + + return Response.status(Response.Status.OK).entity(m_serializer.serialize(result, ui)).build(); + } + + @GET + @Path("{entityId}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getEntity(@Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("entityId") String entityId) throws CatalogException { + + BaseRequest request = new InstanceRequest(Collections.<String, Object>singletonMap("id", entityId)); + Result result = getResource(entityResourceProvider, request); + + return Response.status(Response.Status.OK).entity(m_serializer.serialize(result, ui)).build(); + } + + @GET + @Path("{entityId}/tags/{tag}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getEntityTag(@Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("entityId") String entityId, + @PathParam("tag") String tagName) throws CatalogException { + + Map<String, Object> properties = new HashMap<>(); + properties.put("id", entityId); + properties.put("name", tagName); + Result result = getResource(entityTagResourceProvider, new InstanceRequest(properties)); + + return Response.status(Response.Status.OK).entity(m_serializer.serialize(result, ui)).build(); + } + + @GET + @Path("{entityId}/tags") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getEntityTags(@Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("entityId") String entityGuid) throws CatalogException { + + BaseRequest request = new CollectionRequest(Collections.<String, Object>singletonMap("id", entityGuid), + decode(getQueryString(ui))); + Result result = getResources(entityTagResourceProvider, request); + + return Response.status(Response.Status.OK).entity(m_serializer.serialize(result, ui)).build(); + } + + @POST + @Path("{entityId}/tags/{tag}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response tagEntity(String body, + @Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("entityId") String entityId, + @PathParam("tag") String tagName) throws CatalogException { + + Map<String, Object> properties = new HashMap<>(); + properties.put("id", entityId); + properties.put("name", tagName); + createResource(entityTagResourceProvider, new InstanceRequest(properties)); + + return Response.status(Response.Status.CREATED).entity( + new Results(ui.getRequestUri().toString(), 201)).build(); + } + + @POST + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response tagEntities(String body, + @Context HttpHeaders headers, + @Context UriInfo ui) throws CatalogException { + + Map<String, Object> properties = parsePayload(body); + + if (properties.get("tags") == null || properties.size() != 1) { + throw new CatalogException( + "Invalid Request, no 'tags' property specified. Creation of entity resource not supported.", 400); + + } + String queryString = decode(getQueryString(ui)); + Collection<String> createResults = createResources( + entityTagResourceProvider, new CollectionRequest(properties, queryString)); + + Collection<Results> result = new ArrayList<>(); + for (String relativeUrl : createResults) { + result.add(new Results(ui.getBaseUri().toString() + relativeUrl, 201)); + } + + return Response.status(Response.Status.CREATED).entity( + new GenericEntity<Collection<Results>>(result) {}).build(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/java/org/apache/atlas/web/resources/TaxonomyService.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/TaxonomyService.java b/webapp/src/main/java/org/apache/atlas/web/resources/TaxonomyService.java new file mode 100644 index 0000000..ccd0b62 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/resources/TaxonomyService.java @@ -0,0 +1,201 @@ +/** + * 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.web.resources; + +import org.apache.atlas.catalog.*; +import org.apache.atlas.catalog.Request; +import org.apache.atlas.catalog.exception.CatalogException; +import org.apache.atlas.catalog.exception.InvalidPayloadException; +import org.apache.atlas.services.MetadataService; +import org.apache.atlas.web.util.Servlets; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Service which handles API requests for taxonomy and term resources. + */ +@Path("v1/taxonomies") +@Singleton +public class TaxonomyService extends BaseService { + + private final TaxonomyResourceProvider taxonomyResourceProvider; + private static TermResourceProvider termResourceProvider; + private static JsonSerializer serializer = new JsonSerializer(); + + @Inject + public TaxonomyService(MetadataService metadataService) { + DefaultTypeSystem typeSystem = new DefaultTypeSystem(metadataService); + taxonomyResourceProvider = new TaxonomyResourceProvider(typeSystem); + termResourceProvider = new TermResourceProvider(typeSystem); + } + + @GET + @Path("{taxonomyName}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getTaxonomy(@Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("taxonomyName") String taxonomyName) throws CatalogException { + + Map<String, Object> properties = new HashMap<>(); + properties.put("name", taxonomyName); + Result result = getResource(taxonomyResourceProvider, new InstanceRequest(properties)); + return Response.status(Response.Status.OK).entity(serializer.serialize(result, ui)).build(); + } + + @GET + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getTaxonomies(@Context HttpHeaders headers, @Context UriInfo ui) throws CatalogException { + String queryString = decode(getQueryString(ui)); + Request request = new CollectionRequest(Collections.<String, Object>emptyMap(), queryString); + Result result = getResources(taxonomyResourceProvider, request); + return Response.status(Response.Status.OK).entity(serializer.serialize(result, ui)).build(); + } + + @POST + @Path("{taxonomyName}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response createTaxonomy(String body, + @Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("taxonomyName") String taxonomyName) throws CatalogException { + + Map<String, Object> properties = parsePayload(body); + properties.put("name", taxonomyName); + + createResource(taxonomyResourceProvider, new InstanceRequest(properties)); + + return Response.status(Response.Status.CREATED).entity( + new Results(ui.getRequestUri().toString(), 201)).build(); + } + + @GET + @Path("{taxonomyName}/terms/{termName}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getTaxonomyTerm(@Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("taxonomyName") String taxonomyName, + @PathParam("termName") String termName) throws CatalogException { + + TermPath termPath = new TermPath(taxonomyName, termName); + Map<String, Object> properties = new HashMap<>(); + properties.put("termPath", termPath); + Result result = getResource(termResourceProvider, new InstanceRequest(properties)); + + return Response.status(Response.Status.OK).entity(serializer.serialize(result, ui)).build(); + } + + @GET + @Path("{taxonomyName}/terms") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getTaxonomyTerms(@Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("taxonomyName") String taxonomyName) throws CatalogException { + + String queryString = decode(getQueryString(ui)); + TermPath termPath = new TermPath(taxonomyName, null); + Request request = new CollectionRequest( + Collections.<String, Object>singletonMap("termPath", termPath), queryString); + Result result = getResources(termResourceProvider, request); + + return Response.status(Response.Status.OK).entity(serializer.serialize(result, ui)).build(); + } + + @GET + @Path("{taxonomyName}/terms/{rootTerm}/{remainder:.*}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getSubTerms(@Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("taxonomyName") String taxonomyName, + @PathParam("rootTerm") String rootTerm, + @PathParam("remainder") String remainder) throws CatalogException { + + Result result; + + List<PathSegment> pathSegments = ui.getPathSegments(); + + int lastIndex = pathSegments.size() - 1; + String lastSegment = pathSegments.get(lastIndex).getPath(); + String termName = String.format("%s%s", rootTerm, + remainder.replaceAll("/?terms/?([.]*)", "$1.")); + String queryString = decode(getQueryString(ui)); + TermPath termPath = new TermPath(taxonomyName, termName); + + Map<String, Object> properties = new HashMap<>(); + properties.put("termPath", termPath); + if (lastSegment.equals("terms") || (lastSegment.isEmpty() && pathSegments.get(lastIndex - 1).getPath().equals("terms"))) { + result = getResources(termResourceProvider, new CollectionRequest(properties, queryString)); + } else { + result = getResource(termResourceProvider, new InstanceRequest(properties)); + } + + return Response.status(Response.Status.OK).entity(serializer.serialize(result, ui)).build(); + } + + @POST + @Path("{taxonomyName}/terms/{termName}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response createTerm(String body, + @Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("taxonomyName") String taxonomyName, + @PathParam("termName") String termName) throws CatalogException { + + Map<String, Object> properties = parsePayload(body); + validateName(termName); + properties.put("termPath", new TermPath(taxonomyName, termName)); + createResource(termResourceProvider, new InstanceRequest(properties)); + + return Response.status(Response.Status.CREATED).entity( + new Results(ui.getRequestUri().toString(), 201)).build(); + } + + @POST + @Path("{taxonomyName}/terms/{termName}/{remainder:.*}") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response createSubTerms(String body, + @Context HttpHeaders headers, + @Context UriInfo ui, + @PathParam("taxonomyName") String taxonomyName, + @PathParam("termName") String termName, + @PathParam("remainder") String remainder) throws CatalogException { + + Map<String, Object> properties = parsePayload(body); + String[] pathTokens = remainder.split("/"); + validateName(pathTokens[pathTokens.length -1]); + properties.put("termPath", new TermPath(taxonomyName, String.format("%s%s", termName, + remainder.replaceAll("/?terms/?([.]*)", "$1.")))); + createResource(termResourceProvider, new InstanceRequest(properties)); + + return Response.status(Response.Status.CREATED).entity( + new Results(ui.getRequestUri().toString(), 201)).build(); + } + + private void validateName(String name) throws InvalidPayloadException { + if (name.contains(".")) { + throw new InvalidPayloadException("The \"name\" property may not contain the character '.'"); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml index f0b606e..34b6856 100755 --- a/webapp/src/main/webapp/WEB-INF/web.xml +++ b/webapp/src/main/webapp/WEB-INF/web.xml @@ -30,6 +30,11 @@ org.apache.atlas.web.resources,org.apache.atlas.web.params </param-value> </context-param> + + <context-param> + <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> + <param-value>true</param-value> + </context-param> <!-- More information can be found here: http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/webapp/src/test/java/org/apache/atlas/authorize/AtlasAuthorizationUtilsTest.java ---------------------------------------------------------------------- diff --git a/webapp/src/test/java/org/apache/atlas/authorize/AtlasAuthorizationUtilsTest.java b/webapp/src/test/java/org/apache/atlas/authorize/AtlasAuthorizationUtilsTest.java new file mode 100644 index 0000000..5fc4420 --- /dev/null +++ b/webapp/src/test/java/org/apache/atlas/authorize/AtlasAuthorizationUtilsTest.java @@ -0,0 +1,121 @@ +/** + * 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.atlas.authorize; + +import org.testng.annotations.Test; + +import java.util.List; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * Unit tests for AtlasAuthorizationUtils. + */ +public class AtlasAuthorizationUtilsTest { + @Test + public void testGetApi() { + String contextPath = "/api/atlas/entities"; + assertEquals(AtlasAuthorizationUtils.getApi(contextPath), "entities"); + + contextPath = "/api/atlas/entities/111/traits"; + assertEquals(AtlasAuthorizationUtils.getApi(contextPath), "entities"); + + contextPath = "/api/atlas/v1/entities"; + assertEquals(AtlasAuthorizationUtils.getApi(contextPath), "v1/entities"); + + contextPath = "/api/atlas/v1/entities/111/tags"; + assertEquals(AtlasAuthorizationUtils.getApi(contextPath), "v1/entities"); + + // not sure of this use case but the code appears to support url's that don't + // begin with base url. + contextPath = "/foo/bar"; + assertEquals(AtlasAuthorizationUtils.getApi(contextPath), "foo"); + } + + @Test + public void testGetAtlasResourceType() throws Exception { + String contextPath = "/api/atlas/types"; + List<AtlasResourceTypes> resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 1); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TYPE)); + + contextPath = "/api/atlas/admin/foo"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 1); + assertTrue(resourceTypes.contains(AtlasResourceTypes.OPERATION)); + + contextPath = "/api/atlas/graph/foo"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 1); + assertTrue(resourceTypes.contains(AtlasResourceTypes.OPERATION)); + + contextPath = "/api/atlas/discovery/search/gremlin"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 1); + assertTrue(resourceTypes.contains(AtlasResourceTypes.OPERATION)); + + contextPath = "/api/atlas/entities/111/traits"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 2); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TYPE)); + + contextPath = "/api/atlas/discovery/search"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 2); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TYPE)); + + contextPath = "/api/atlas/entities?type=Column"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 1); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + + contextPath = "/api/atlas/lineage"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 1); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + + contextPath = "/api/atlas/v1/taxonomies"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 2); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TAXONOMY)); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + + contextPath = "/api/atlas/v1/taxonomies/taxonomy1/terms"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 4); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TAXONOMY)); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TERM)); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TYPE)); + + contextPath = "/api/atlas/v1/entities/111"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 1); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + + contextPath = "/api/atlas/v1/entities/111/tags/foo"; + resourceTypes = AtlasAuthorizationUtils.getAtlasResourceType(contextPath); + assertEquals(resourceTypes.size(), 2); + assertTrue(resourceTypes.contains(AtlasResourceTypes.ENTITY)); + assertTrue(resourceTypes.contains(AtlasResourceTypes.TYPE)); + } +}
