/*
* Copyright © 2016 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.data2.audit;
import co.cask.cdap.data2.metadata.lineage.AccessType;
import co.cask.cdap.proto.audit.AuditPayload;
import co.cask.cdap.proto.audit.AuditType;
import co.cask.cdap.proto.audit.payload.access.AccessPayload;
import co.cask.cdap.proto.id.EntityIdCompatible;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
/**
* Helper class to publish audit.
*/
public final class AuditPublishers {
private static final Logger LOG = LoggerFactory.getLogger(AuditPublishers.class);
private static final AtomicBoolean WARNING_LOGGED = new AtomicBoolean(false);
private static final Integer ACCESS_CACHE_MAX_SIZE = 1024;
private static final Cache<AccessAuditInfo, Boolean> CACHE_AUDIT_LOGS =
CacheBuilder.newBuilder().expireAfterWrite(1L, TimeUnit.DAYS).maximumSize(ACCESS_CACHE_MAX_SIZE).build();
private AuditPublishers() {}
/**
* Publish access audit information using {@link AuditPublisher}.
*
* @param publisher audit publisher, if null no audit information is published
* @param entityId entity id for which audit information is being published
* @param accessType access type
* @param accessor the entity accessing entityId
*/
public static void publishAccess(@Nullable AuditPublisher publisher, EntityIdCompatible entityId,
AccessType accessType, EntityIdCompatible accessor) {
if (publisher == null) {
logWarning();
return;
}
AccessAuditInfo accessAuditInfo = new AccessAuditInfo(accessor, entityId, accessType);
synchronized (CACHE_AUDIT_LOGS) {
if (CACHE_AUDIT_LOGS.getIfPresent(accessAuditInfo) != null) {
// this access has already been published recently (since it is present in the cache). hence don't publish again
return;
}
CACHE_AUDIT_LOGS.put(accessAuditInfo, true);
}
switch (accessType) {
case READ:
publisher.publish(entityId.toEntityId(), AuditType.ACCESS,
new AccessPayload(co.cask.cdap.proto.audit.payload.access.AccessType.READ,
accessor.toEntityId()));
break;
case WRITE:
publisher.publish(entityId.toEntityId(), AuditType.ACCESS,
new AccessPayload(co.cask.cdap.proto.audit.payload.access.AccessType.WRITE,
accessor.toEntityId()));
break;
case READ_WRITE:
publisher.publish(entityId.toEntityId(), AuditType.ACCESS,
new AccessPayload(co.cask.cdap.proto.audit.payload.access.AccessType.READ,
accessor.toEntityId()));
publisher.publish(entityId.toEntityId(), AuditType.ACCESS,
new AccessPayload(co.cask.cdap.proto.audit.payload.access.AccessType.WRITE,
accessor.toEntityId()));
break;
case UNKNOWN:
publisher.publish(entityId.toEntityId(), AuditType.ACCESS,
new AccessPayload(co.cask.cdap.proto.audit.payload.access.AccessType.UNKNOWN,
accessor.toEntityId()));
break;
}
}
/**
* Publish audit information using {@link AuditPublisher}.
*
* @param publisher audit publisher, if null no audit information is published
* @param entityId entity id for which audit information is being published
* @param auditType audit type
* @param auditPayload audit payload
*/
public static void publishAudit(@Nullable AuditPublisher publisher, EntityIdCompatible entityId,
AuditType auditType, AuditPayload auditPayload) {
if (publisher == null) {
logWarning();
return;
}
publisher.publish(entityId.toEntityId(), auditType, auditPayload);
}
/**
* Logs warning about not having audit publisher. The warning is logged only once.
*/
private static void logWarning() {
if (!WARNING_LOGGED.get()) {
LOG.warn("Audit publisher is null, audit information will not be published");
WARNING_LOGGED.set(true);
}
}
/**
* Contains the accessed entity info and the access type.
*/
private static class AccessAuditInfo {
private final EntityIdCompatible accessorEntity;
private final EntityIdCompatible accessedEntity;
private final AccessType accessType;
AccessAuditInfo(EntityIdCompatible accessorEntity, EntityIdCompatible accessedEntity, AccessType accessType) {
this.accessorEntity = accessorEntity;
this.accessedEntity = accessedEntity;
this.accessType = accessType;
}
public EntityIdCompatible getAccessorEntity() {
return accessorEntity;
}
public EntityIdCompatible getAccessedEntity() {
return accessedEntity;
}
public AccessType getAccessType() {
return accessType;
}
@Override
public int hashCode() {
return Objects.hash(accessorEntity, accessedEntity, accessType);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
AccessAuditInfo that = (AccessAuditInfo) obj;
return Objects.equals(accessorEntity, that.accessorEntity) &&
Objects.equals(accessedEntity, that.accessedEntity) &&
Objects.equals(accessType, that.accessType);
}
@Override
public String toString() {
return "AccessedEntityInfo{" +
"accessorEntity='" + accessorEntity + '\'' +
"accessedEntity='" + accessedEntity + '\'' +
", accessType='" + accessType +
'}';
}
}
}