Repository: incubator-atlas Updated Branches: refs/heads/master 85afbefc0 -> c2356f8ef
http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/c2356f8e/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 a2c347d..13d20d8 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 @@ -19,6 +19,7 @@ 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.Referenceable; import org.apache.atlas.typesystem.types.DataTypes; @@ -207,4 +208,13 @@ public interface MetadataService { * @throws AtlasException */ List<String> deleteEntityByUniqueAttribute(String typeName, String uniqueAttributeName, String attrValue) throws AtlasException; + + /** + * Returns entity audit events for entity id in the decreasing order of timestamp + * @param guid entity id + * @param startKey key for the first event, used for pagination + * @param count number of events to be returned + * @return + */ + List<EntityAuditEvent> getAuditEvents(String guid, String startKey, short count) throws AtlasException; } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/c2356f8e/webapp/src/main/java/org/apache/atlas/web/listeners/GuiceServletConfig.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/listeners/GuiceServletConfig.java b/webapp/src/main/java/org/apache/atlas/web/listeners/GuiceServletConfig.java index 6bfd780..1eca174 100755 --- a/webapp/src/main/java/org/apache/atlas/web/listeners/GuiceServletConfig.java +++ b/webapp/src/main/java/org/apache/atlas/web/listeners/GuiceServletConfig.java @@ -42,6 +42,7 @@ import org.apache.atlas.web.filters.ActiveServerFilter; import org.apache.atlas.web.filters.AtlasAuthenticationFilter; import org.apache.atlas.web.filters.AuditFilter; import org.apache.atlas.web.service.ActiveInstanceElectorModule; +import org.apache.atlas.web.service.ServiceModule; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.slf4j.Logger; @@ -76,7 +77,7 @@ public class GuiceServletConfig extends GuiceServletContextListener { loginProcessor.login(); injector = Guice.createInjector(getRepositoryModule(), new ActiveInstanceElectorModule(), - new NotificationModule(), new JerseyServletModule() { + new NotificationModule(), new ServiceModule(), new JerseyServletModule() { private Configuration appConfiguration = null; http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/c2356f8e/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java b/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java index f39a80e..8e69378 100755 --- a/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java +++ b/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java @@ -21,12 +21,13 @@ package org.apache.atlas.web.resources; import com.google.common.base.Preconditions; import org.apache.atlas.AtlasClient; import org.apache.atlas.AtlasException; +import org.apache.atlas.EntityAuditEvent; import org.apache.atlas.services.MetadataService; import org.apache.atlas.typesystem.Referenceable; import org.apache.atlas.typesystem.exception.EntityExistsException; import org.apache.atlas.typesystem.exception.EntityNotFoundException; -import org.apache.atlas.typesystem.exception.TypeNotFoundException; import org.apache.atlas.typesystem.exception.TraitNotFoundException; +import org.apache.atlas.typesystem.exception.TypeNotFoundException; import org.apache.atlas.typesystem.json.InstanceSerialization; import org.apache.atlas.typesystem.types.ValueConversionException; import org.apache.atlas.utils.ParamChecker; @@ -43,6 +44,7 @@ import javax.inject.Singleton; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -52,11 +54,13 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.net.URI; import java.util.ArrayList; +import java.util.Collection; import java.util.List; @@ -95,7 +99,7 @@ public class EntityResource { * unique attribute for the give type. */ @POST - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response submit(@Context HttpServletRequest request) { try { @@ -150,7 +154,7 @@ public class EntityResource { * @return response payload as json */ @PUT - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response updateEntities(@Context HttpServletRequest request) { try { @@ -195,7 +199,7 @@ public class EntityResource { */ @POST @Path("qualifiedName") - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response updateByUniqueAttribute(@QueryParam("type") String entityType, @QueryParam("property") String attribute, @@ -242,7 +246,7 @@ public class EntityResource { */ @POST @Path("{guid}") - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response updateEntityByGuid(@PathParam("guid") String guid, @QueryParam("property") String attribute, @Context HttpServletRequest request) { @@ -327,7 +331,6 @@ public class EntityResource { * @return response payload as json - including guids of entities(including composite references from that entity) that were deleted */ @DELETE - @Consumes(Servlets.JSON_MEDIA_TYPE) @Produces(Servlets.JSON_MEDIA_TYPE) public Response deleteEntities(@QueryParam("guid") List<String> guids, @QueryParam("type") String entityType, @@ -439,6 +442,7 @@ public class EntityResource { } @GET + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response getEntity(@QueryParam("type") String entityType, @QueryParam("property") String attribute, @@ -537,7 +541,7 @@ public class EntityResource { */ @POST @Path("{guid}/traits") - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response addTrait(@Context HttpServletRequest request, @PathParam("guid") String guid) { try { @@ -573,7 +577,7 @@ public class EntityResource { */ @DELETE @Path("{guid}/traits/{traitName}") - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response deleteTrait(@Context HttpServletRequest request, @PathParam("guid") String guid, @PathParam(TRAIT_NAME) String traitName) { @@ -601,4 +605,45 @@ public class EntityResource { throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR)); } } + + /** + * Returns the entity audit events for a given entity id. The events are returned in the decreasing order of timestamp. + * @param guid entity id + * @param startKey used for pagination. Startkey is inclusive, the returned results contain the event with the given startkey. + * First time getAuditEvents() is called for an entity, startKey should be null, + * with count = (number of events required + 1). Next time getAuditEvents() is called for the same entity, + * startKey should be equal to the entityKey of the last event returned in the previous call. + * @param count number of events required + * @return + */ + @GET + @Path("{guid}/audit") + @Produces(Servlets.JSON_MEDIA_TYPE) + public Response getAuditEvents(@PathParam("guid") String guid, @QueryParam("startKey") String startKey, + @QueryParam("count") @DefaultValue("100") short count) { + LOG.debug("Audit events request for entity {}, start key {}, number of results required {}", guid, startKey, + count); + try { + List<EntityAuditEvent> events = metadataService.getAuditEvents(guid, startKey, count); + + JSONObject response = new JSONObject(); + response.put(AtlasClient.REQUEST_ID, Servlets.getRequestId()); + response.put(AtlasClient.EVENTS, getJSONArray(events)); + return Response.ok(response).build(); + } catch (AtlasException | IllegalArgumentException e) { + LOG.error("Unable to get audit events for entity {}", guid, e); + throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.BAD_REQUEST)); + } catch (Throwable e) { + LOG.error("Unable to get audit events for entity {}", guid, e); + throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR)); + } + } + + private <T> JSONArray getJSONArray(Collection<T> elements) throws JSONException { + JSONArray jsonArray = new JSONArray(); + for(T element : elements) { + jsonArray.put(new JSONObject(element.toString())); + } + return jsonArray; + } } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/c2356f8e/webapp/src/main/java/org/apache/atlas/web/resources/TypesResource.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/TypesResource.java b/webapp/src/main/java/org/apache/atlas/web/resources/TypesResource.java index 8b0d0e9..74d89ce 100755 --- a/webapp/src/main/java/org/apache/atlas/web/resources/TypesResource.java +++ b/webapp/src/main/java/org/apache/atlas/web/resources/TypesResource.java @@ -45,6 +45,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; @@ -76,7 +77,7 @@ public class TypesResource { * domain. Could represent things like Hive Database, Hive Table, etc. */ @POST - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response submit(@Context HttpServletRequest request) { try { @@ -120,7 +121,7 @@ public class TypesResource { * @return */ @PUT - @Consumes(Servlets.JSON_MEDIA_TYPE) + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) @Produces(Servlets.JSON_MEDIA_TYPE) public Response update(@Context HttpServletRequest request) { try { http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/c2356f8e/webapp/src/main/java/org/apache/atlas/web/service/ActiveInstanceElectorModule.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/service/ActiveInstanceElectorModule.java b/webapp/src/main/java/org/apache/atlas/web/service/ActiveInstanceElectorModule.java index 065666d..ee6035d 100644 --- a/webapp/src/main/java/org/apache/atlas/web/service/ActiveInstanceElectorModule.java +++ b/webapp/src/main/java/org/apache/atlas/web/service/ActiveInstanceElectorModule.java @@ -22,6 +22,7 @@ import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.apache.atlas.listener.ActiveStateChangeHandler; import org.apache.atlas.notification.NotificationHookConsumer; +import org.apache.atlas.repository.audit.HBaseBasedAuditRepository; import org.apache.atlas.repository.graph.GraphBackedSearchIndexer; import org.apache.atlas.service.Service; import org.apache.atlas.services.DefaultMetadataService; @@ -39,8 +40,7 @@ public class ActiveInstanceElectorModule extends AbstractModule { activeStateChangeHandlerBinder.addBinding().to(GraphBackedSearchIndexer.class); activeStateChangeHandlerBinder.addBinding().to(DefaultMetadataService.class); activeStateChangeHandlerBinder.addBinding().to(NotificationHookConsumer.class); - //Enable this after ATLAS-498 is committed - //activeStateChangeHandlerBinder.addBinding().to(HBaseBasedAuditRepository.class); + activeStateChangeHandlerBinder.addBinding().to(HBaseBasedAuditRepository.class); Multibinder<Service> serviceBinder = Multibinder.newSetBinder(binder(), Service.class); serviceBinder.addBinding().to(ActiveInstanceElectorService.class); http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/c2356f8e/webapp/src/main/java/org/apache/atlas/web/service/ServiceModule.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/service/ServiceModule.java b/webapp/src/main/java/org/apache/atlas/web/service/ServiceModule.java new file mode 100644 index 0000000..0f8bcb1 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/service/ServiceModule.java @@ -0,0 +1,41 @@ +/** + * 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.service; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import org.apache.atlas.kafka.KafkaNotification; +import org.apache.atlas.listener.EntityChangeListener; +import org.apache.atlas.notification.NotificationHookConsumer; +import org.apache.atlas.notification.entity.NotificationEntityChangeListener; +import org.apache.atlas.service.Service; + +public class ServiceModule extends AbstractModule { + @Override + protected void configure() { + Multibinder<Service> serviceBinder = Multibinder.newSetBinder(binder(), Service.class); + serviceBinder.addBinding().to(KafkaNotification.class); + serviceBinder.addBinding().to(NotificationHookConsumer.class); + + //Add NotificationEntityChangeListener as EntityChangeListener + Multibinder<EntityChangeListener> entityChangeListenerBinder = + Multibinder.newSetBinder(binder(), EntityChangeListener.class); + entityChangeListenerBinder.addBinding().to(NotificationEntityChangeListener.class); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/c2356f8e/webapp/src/test/java/org/apache/atlas/web/resources/EntityJerseyResourceIT.java ---------------------------------------------------------------------- diff --git a/webapp/src/test/java/org/apache/atlas/web/resources/EntityJerseyResourceIT.java b/webapp/src/test/java/org/apache/atlas/web/resources/EntityJerseyResourceIT.java index 6a939d3..83db46f 100755 --- a/webapp/src/test/java/org/apache/atlas/web/resources/EntityJerseyResourceIT.java +++ b/webapp/src/test/java/org/apache/atlas/web/resources/EntityJerseyResourceIT.java @@ -26,6 +26,7 @@ import com.sun.jersey.api.client.WebResource; import org.apache.atlas.AtlasClient; import org.apache.atlas.AtlasServiceException; +import org.apache.atlas.EntityAuditEvent; import org.apache.atlas.notification.NotificationConsumer; import org.apache.atlas.notification.NotificationInterface; import org.apache.atlas.notification.NotificationModule; @@ -146,6 +147,7 @@ public class EntityJerseyResourceIT extends BaseResourceIT { db.set("description", randomString()); final String dbid = serviceClient.createEntity(db).getString(0); + assertEntityAudit(dbid, EntityAuditEvent.EntityAuditAction.ENTITY_CREATE); waitForNotification(notificationConsumer, MAX_WAIT_TIME, new NotificationPredicate() { @Override @@ -187,6 +189,17 @@ public class EntityJerseyResourceIT extends BaseResourceIT { assertEquals(results.length(), 1); } + private void assertEntityAudit(String dbid, EntityAuditEvent.EntityAuditAction auditAction) + throws Exception { + List<EntityAuditEvent> events = serviceClient.getEntityAuditEvents(dbid, (short) 100); + for (EntityAuditEvent event : events) { + if (event.getAction() == auditAction) { + return; + } + } + fail("Expected audit event with action = " + auditAction); + } + @Test public void testEntityDefinitionAcrossTypeUpdate() throws Exception { //create type @@ -478,6 +491,8 @@ public class EntityJerseyResourceIT extends BaseResourceIT { JSONObject response = new JSONObject(responseAsString); Assert.assertNotNull(response.get(AtlasClient.REQUEST_ID)); Assert.assertNotNull(response.get(AtlasClient.GUID)); + + assertEntityAudit(guid, EntityAuditEvent.EntityAuditAction.TAG_ADD); } @Test(dependsOnMethods = "testAddTrait") @@ -576,6 +591,7 @@ public class EntityJerseyResourceIT extends BaseResourceIT { Assert.assertNotNull(response.get(AtlasClient.REQUEST_ID)); Assert.assertNotNull(response.get("GUID")); Assert.assertNotNull(response.get("traitName")); + assertEntityAudit(guid, EntityAuditEvent.EntityAuditAction.TAG_DELETE); } @Test
