blob: eab86c4fa0295963ba0f737e2526be223619f52d [file] [log] [blame]
/**
* 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.repository.audit;
import org.apache.atlas.AtlasException;
import org.apache.atlas.EntityAuditEvent;
import org.apache.atlas.EntityAuditEvent.EntityAuditAction;
import org.apache.atlas.RequestContextV1;
import org.apache.atlas.listener.EntityChangeListener;
import org.apache.atlas.typesystem.IReferenceableInstance;
import org.apache.atlas.typesystem.IStruct;
import org.apache.atlas.typesystem.ITypedReferenceableInstance;
import org.apache.atlas.typesystem.json.InstanceSerialization;
import org.apache.atlas.typesystem.types.AttributeInfo;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Listener on entity create/update/delete, tag add/delete. Adds the corresponding audit event to the audit repository.
*/
public class EntityAuditListener implements EntityChangeListener {
private static final Logger LOG = LoggerFactory.getLogger(EntityAuditListener.class);
private EntityAuditRepository auditRepository;
@Inject
public EntityAuditListener(EntityAuditRepository auditRepository) {
this.auditRepository = auditRepository;
}
@Override
public void onEntitiesAdded(Collection<ITypedReferenceableInstance> entities, boolean isImport) throws AtlasException {
List<EntityAuditEvent> events = new ArrayList<>();
for (ITypedReferenceableInstance entity : entities) {
EntityAuditEvent event = createEvent(entity, isImport ? EntityAuditAction.ENTITY_IMPORT_CREATE : EntityAuditAction.ENTITY_CREATE);
events.add(event);
}
auditRepository.putEvents(events);
}
@Override
public void onEntitiesUpdated(Collection<ITypedReferenceableInstance> entities, boolean isImport) throws AtlasException {
List<EntityAuditEvent> events = new ArrayList<>();
for (ITypedReferenceableInstance entity : entities) {
EntityAuditEvent event = createEvent(entity, isImport ? EntityAuditAction.ENTITY_IMPORT_UPDATE : EntityAuditAction.ENTITY_UPDATE);
events.add(event);
}
auditRepository.putEvents(events);
}
@Override
public void onTraitsAdded(ITypedReferenceableInstance entity, Collection<? extends IStruct> traits) throws AtlasException {
if (traits != null) {
for (IStruct trait : traits) {
EntityAuditEvent event = createEvent(entity, EntityAuditAction.TAG_ADD,
"Added trait: " + InstanceSerialization.toJson(trait, true));
auditRepository.putEvents(event);
}
}
}
@Override
public void onTraitsDeleted(ITypedReferenceableInstance entity, Collection<String> traitNames) throws AtlasException {
if (traitNames != null) {
for (String traitName : traitNames) {
EntityAuditEvent event = createEvent(entity, EntityAuditAction.TAG_DELETE, "Deleted trait: " + traitName);
auditRepository.putEvents(event);
}
}
}
@Override
public void onTraitsUpdated(ITypedReferenceableInstance entity, Collection<? extends IStruct> traits) throws AtlasException {
if (traits != null) {
for (IStruct trait : traits) {
EntityAuditEvent event = createEvent(entity, EntityAuditAction.TAG_UPDATE,
"Updated trait: " + InstanceSerialization.toJson(trait, true));
auditRepository.putEvents(event);
}
}
}
@Override
public void onEntitiesDeleted(Collection<ITypedReferenceableInstance> entities, boolean isImport) throws AtlasException {
List<EntityAuditEvent> events = new ArrayList<>();
for (ITypedReferenceableInstance entity : entities) {
EntityAuditEvent event = createEvent(entity, isImport ? EntityAuditAction.ENTITY_IMPORT_DELETE : EntityAuditAction.ENTITY_DELETE, "Deleted entity");
events.add(event);
}
auditRepository.putEvents(events);
}
public List<EntityAuditEvent> getAuditEvents(String guid) throws AtlasException{
return auditRepository.listEvents(guid, null, (short) 10);
}
private EntityAuditEvent createEvent(ITypedReferenceableInstance entity, EntityAuditAction action)
throws AtlasException {
String detail = getAuditEventDetail(entity, action);
return createEvent(entity, action, detail);
}
private EntityAuditEvent createEvent(ITypedReferenceableInstance entity, EntityAuditAction action, String details)
throws AtlasException {
return new EntityAuditEvent(entity.getId()._getId(), RequestContextV1.get().getRequestTime(), RequestContextV1.get().getUser(), action, details, entity);
}
private String getAuditEventDetail(ITypedReferenceableInstance entity, EntityAuditAction action) throws AtlasException {
Map<String, Object> prunedAttributes = pruneEntityAttributesForAudit(entity);
String auditPrefix = getAuditPrefix(action);
String auditString = auditPrefix + InstanceSerialization.toJson(entity, true);
byte[] auditBytes = auditString.getBytes(StandardCharsets.UTF_8);
long auditSize = auditBytes != null ? auditBytes.length : 0;
long auditMaxSize = auditRepository.repositoryMaxSize();
if (auditMaxSize >= 0 && auditSize > auditMaxSize) { // don't store attributes in audit
LOG.warn("audit record too long: entityType={}, guid={}, size={}; maxSize={}. entity attribute values not stored in audit",
entity.getTypeName(), entity.getId()._getId(), auditSize, auditMaxSize);
Map<String, Object> attrValues = entity.getValuesMap();
clearAttributeValues(entity);
auditString = auditPrefix + InstanceSerialization.toJson(entity, true);
addAttributeValues(entity, attrValues);
}
restoreEntityAttributes(entity, prunedAttributes);
return auditString;
}
private void clearAttributeValues(IReferenceableInstance entity) throws AtlasException {
Map<String, Object> attributesMap = entity.getValuesMap();
if (MapUtils.isNotEmpty(attributesMap)) {
for (String attribute : attributesMap.keySet()) {
entity.setNull(attribute);
}
}
}
private void addAttributeValues(ITypedReferenceableInstance entity, Map<String, Object> attributesMap) throws AtlasException {
if (MapUtils.isNotEmpty(attributesMap)) {
for (String attr : attributesMap.keySet()) {
entity.set(attr, attributesMap.get(attr));
}
}
}
private Map<String, Object> pruneEntityAttributesForAudit(ITypedReferenceableInstance entity) throws AtlasException {
Map<String, Object> ret = null;
Map<String, Object> entityAttributes = entity.getValuesMap();
List<String> excludeAttributes = auditRepository.getAuditExcludeAttributes(entity.getTypeName());
if (CollectionUtils.isNotEmpty(excludeAttributes) && MapUtils.isNotEmpty(entityAttributes)) {
Map<String, AttributeInfo> attributeInfoMap = entity.fieldMapping().fields;
for (String attrName : entityAttributes.keySet()) {
Object attrValue = entityAttributes.get(attrName);
AttributeInfo attrInfo = attributeInfoMap.get(attrName);
if (excludeAttributes.contains(attrName)) {
if (ret == null) {
ret = new HashMap<>();
}
ret.put(attrName, attrValue);
entity.setNull(attrName);
} else if (attrInfo.isComposite) {
if (attrValue instanceof Collection) {
for (Object attribute : (Collection) attrValue) {
if (attribute instanceof ITypedReferenceableInstance) {
ret = pruneAttributes(ret, (ITypedReferenceableInstance) attribute);
}
}
} else if (attrValue instanceof ITypedReferenceableInstance) {
ret = pruneAttributes(ret, (ITypedReferenceableInstance) attrValue);
}
}
}
}
return ret;
}
private Map<String, Object> pruneAttributes(Map<String, Object> ret, ITypedReferenceableInstance attribute) throws AtlasException {
ITypedReferenceableInstance attrInstance = attribute;
Map<String, Object> prunedAttrs = pruneEntityAttributesForAudit(attrInstance);
if (MapUtils.isNotEmpty(prunedAttrs)) {
if (ret == null) {
ret = new HashMap<>();
}
ret.put(attrInstance.getId()._getId(), prunedAttrs);
}
return ret;
}
private void restoreEntityAttributes(ITypedReferenceableInstance entity, Map<String, Object> prunedAttributes) throws AtlasException {
if (MapUtils.isEmpty(prunedAttributes)) {
return;
}
Map<String, Object> entityAttributes = entity.getValuesMap();
if (MapUtils.isNotEmpty(entityAttributes)) {
Map<String, AttributeInfo> attributeInfoMap = entity.fieldMapping().fields;
for (String attrName : entityAttributes.keySet()) {
Object attrValue = entityAttributes.get(attrName);
AttributeInfo attrInfo = attributeInfoMap.get(attrName);
if (prunedAttributes.containsKey(attrName)) {
entity.set(attrName, prunedAttributes.get(attrName));
} else if (attrInfo.isComposite) {
if (attrValue instanceof Collection) {
for (Object attributeEntity : (Collection) attrValue) {
if (attributeEntity instanceof ITypedReferenceableInstance) {
restoreAttributes(prunedAttributes, (ITypedReferenceableInstance) attributeEntity);
}
}
} else if (attrValue instanceof ITypedReferenceableInstance) {
restoreAttributes(prunedAttributes, (ITypedReferenceableInstance) attrValue);
}
}
}
}
}
private void restoreAttributes(Map<String, Object> prunedAttributes, ITypedReferenceableInstance attributeEntity) throws AtlasException {
Object obj = prunedAttributes.get(attributeEntity.getId()._getId());
if (obj instanceof Map) {
restoreEntityAttributes(attributeEntity, (Map) obj);
}
}
private String getAuditPrefix(EntityAuditAction action) {
final String ret;
switch (action) {
case ENTITY_CREATE:
ret = "Created: ";
break;
case ENTITY_UPDATE:
ret = "Updated: ";
break;
case ENTITY_DELETE:
ret = "Deleted: ";
break;
case TAG_ADD:
ret = "Added trait: ";
break;
case TAG_DELETE:
ret = "Deleted trait: ";
break;
case TAG_UPDATE:
ret = "Updated trait: ";
break;
case ENTITY_IMPORT_CREATE:
ret = "Created by import: ";
break;
case ENTITY_IMPORT_UPDATE:
ret = "Updated by import: ";
break;
case ENTITY_IMPORT_DELETE:
ret = "Deleted by import: ";
break;
default:
ret = "Unknown: ";
}
return ret;
}
}