/*
* 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.nifi.provenance;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.events.EventReporter;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.processor.DataUnit;
import org.apache.nifi.provenance.lineage.ComputeLineageSubmission;
import org.apache.nifi.provenance.lineage.FlowFileLineage;
import org.apache.nifi.provenance.lineage.Lineage;
import org.apache.nifi.provenance.lineage.LineageComputationType;
import org.apache.nifi.provenance.search.Query;
import org.apache.nifi.provenance.search.QueryResult;
import org.apache.nifi.provenance.search.QuerySubmission;
import org.apache.nifi.provenance.search.SearchTerm;
import org.apache.nifi.provenance.search.SearchableField;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.RingBuffer;
import org.apache.nifi.util.RingBuffer.Filter;
import org.apache.nifi.util.RingBuffer.ForEachEvaluator;
import org.apache.nifi.util.RingBuffer.IterationDirection;
import org.apache.nifi.web.ResourceNotFoundException;
public class VolatileProvenanceRepository implements ProvenanceRepository {
// properties
public static final String BUFFER_SIZE = "nifi.provenance.repository.buffer.size";
// default property values
public static final int DEFAULT_BUFFER_SIZE = 10000;
private final RingBuffer<ProvenanceEventRecord> ringBuffer;
private final List<SearchableField> searchableFields;
private final List<SearchableField> searchableAttributes;
private final ExecutorService queryExecService;
private final ScheduledExecutorService scheduledExecService;
private final ConcurrentMap<String, AsyncQuerySubmission> querySubmissionMap = new ConcurrentHashMap<>();
private final ConcurrentMap<String, AsyncLineageSubmission> lineageSubmissionMap = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(0L);
private final AtomicBoolean initialized = new AtomicBoolean(false);
private Authorizer authorizer; // effectively final
private ProvenanceAuthorizableFactory resourceFactory; // effectively final
/**
* Default no args constructor for service loading only
*/
public VolatileProvenanceRepository() {
ringBuffer = null;
searchableFields = null;
searchableAttributes = null;
queryExecService = null;
scheduledExecService = null;
authorizer = null;
resourceFactory = null;
}
public VolatileProvenanceRepository(final NiFiProperties nifiProperties) {
final int bufferSize = nifiProperties.getIntegerProperty(BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
ringBuffer = new RingBuffer<>(bufferSize);
final String indexedFieldString = nifiProperties.getProperty(NiFiProperties.PROVENANCE_INDEXED_FIELDS);
final String indexedAttrString = nifiProperties.getProperty(NiFiProperties.PROVENANCE_INDEXED_ATTRIBUTES);
searchableFields = Collections.unmodifiableList(SearchableFieldParser.extractSearchableFields(indexedFieldString, true));
searchableAttributes = Collections.unmodifiableList(SearchableFieldParser.extractSearchableFields(indexedAttrString, false));
final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
queryExecService = Executors.newFixedThreadPool(2, new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger(0);
@Override
public Thread newThread(final Runnable r) {
final Thread thread = defaultThreadFactory.newThread(r);
thread.setName("Provenance Query Thread-" + counter.incrementAndGet());
return thread;
}
});
scheduledExecService = Executors.newScheduledThreadPool(2);
}
@Override
public void initialize(final EventReporter eventReporter, final Authorizer authorizer, final ProvenanceAuthorizableFactory resourceFactory,
final IdentifierLookup idLookup) throws IOException {
if (initialized.getAndSet(true)) {
return;
}
this.authorizer = authorizer;
this.resourceFactory = resourceFactory;
scheduledExecService.scheduleWithFixedDelay(new RemoveExpiredQueryResults(), 30L, 30L, TimeUnit.SECONDS);
}
@Override
public ProvenanceEventRepository getProvenanceEventRepository() {
return this;
}
@Override
public ProvenanceEventBuilder eventBuilder() {
return new StandardProvenanceEventRecord.Builder();
}
@Override
public void registerEvent(final ProvenanceEventRecord event) {
final long id = idGenerator.getAndIncrement();
ringBuffer.add(new IdEnrichedProvEvent(event, id));
}
@Override
public void registerEvents(final Iterable<ProvenanceEventRecord> events) {
for (final ProvenanceEventRecord event : events) {
registerEvent(event);
}
}
@Override
public List<ProvenanceEventRecord> getEvents(final long firstRecordId, final int maxRecords) throws IOException {
return getEvents(firstRecordId, maxRecords, null);
}
@Override
public List<ProvenanceEventRecord> getEvents(final long firstRecordId, final int maxRecords, final NiFiUser user) throws IOException {
return ringBuffer.getSelectedElements(new Filter<ProvenanceEventRecord>() {
@Override
public boolean select(final ProvenanceEventRecord value) {
if (user != null && !isAuthorized(value, user)) {
return false;
}
return value.getEventId() >= firstRecordId;
}
}, maxRecords);
}
@Override
public Long getMaxEventId() {
final ProvenanceEventRecord newest = ringBuffer.getNewestElement();
return (newest == null) ? null : newest.getEventId();
}
public ProvenanceEventRecord getEvent(final String identifier) throws IOException {
final List<ProvenanceEventRecord> records = ringBuffer.getSelectedElements(new Filter<ProvenanceEventRecord>() {
@Override
public boolean select(final ProvenanceEventRecord event) {
return identifier.equals(event.getFlowFileUuid());
}
}, 1);
return records.isEmpty() ? null : records.get(0);
}
@Override
public ProvenanceEventRecord getEvent(final long id) {
final List<ProvenanceEventRecord> records = ringBuffer.getSelectedElements(new Filter<ProvenanceEventRecord>() {
@Override
public boolean select(final ProvenanceEventRecord event) {
return event.getEventId() == id;
}
}, 1);
return records.isEmpty() ? null : records.get(0);
}
@Override
public ProvenanceEventRecord getEvent(final long id, final NiFiUser user) {
final ProvenanceEventRecord event = getEvent(id);
if (event == null) {
return null;
}
authorize(event, user);
return event;
}
@Override
public void close() throws IOException {
queryExecService.shutdownNow();
scheduledExecService.shutdown();
}
@Override
public List<SearchableField> getSearchableFields() {
return searchableFields;
}
@Override
public List<SearchableField> getSearchableAttributes() {
return searchableAttributes;
}
public QueryResult queryEvents(final Query query, final NiFiUser user) throws IOException {
final QuerySubmission submission = submitQuery(query, user);
final QueryResult result = submission.getResult();
while (!result.isFinished()) {
try {
Thread.sleep(100L);
} catch (final InterruptedException ie) {
}
}
if (result.getError() != null) {
throw new IOException(result.getError());
}
return result;
}
public boolean isAuthorized(final ProvenanceEventRecord event, final NiFiUser user) {
if (authorizer == null) {
return true;
}
final Authorizable eventAuthorizable;
try {
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
} catch (final ResourceNotFoundException rnfe) {
return false;
}
final AuthorizationResult result = eventAuthorizable.checkAuthorization(authorizer, RequestAction.READ, user, event.getAttributes());
return Result.Approved.equals(result.getResult());
}
protected void authorize(final ProvenanceEventRecord event, final NiFiUser user) {
if (authorizer == null) {
return;
}
final Authorizable eventAuthorizable;
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable.authorize(authorizer, RequestAction.READ, user, event.getAttributes());
}
private Filter<ProvenanceEventRecord> createFilter(final Query query, final NiFiUser user) {
return new Filter<ProvenanceEventRecord>() {
@Override
public boolean select(final ProvenanceEventRecord event) {
if (!isAuthorized(event, user)) {
return false;
}
if (query.getStartDate() != null && query.getStartDate().getTime() > event.getEventTime()) {
return false;
}
if (query.getEndDate() != null && query.getEndDate().getTime() < event.getEventTime()) {
return false;
}
if (query.getMaxFileSize() != null) {
final long maxFileSize = DataUnit.parseDataSize(query.getMaxFileSize(), DataUnit.B).longValue();
if (event.getFileSize() > maxFileSize) {
return false;
}
}
if (query.getMinFileSize() != null) {
final long minFileSize = DataUnit.parseDataSize(query.getMinFileSize(), DataUnit.B).longValue();
if (event.getFileSize() < minFileSize) {
return false;
}
}
for (final SearchTerm searchTerm : query.getSearchTerms()) {
final SearchableField searchableField = searchTerm.getSearchableField();
final String searchValue = searchTerm.getValue();
if (searchableField.isAttribute()) {
final String attributeName = searchableField.getIdentifier();
final String eventAttributeValue = event.getAttributes().get(attributeName);
if (searchValue.contains("?") || searchValue.contains("*")) {
if (eventAttributeValue == null || eventAttributeValue.isEmpty()) {
return false;
}
final String regex = searchValue.replace("?", ".").replace("*", ".*");
final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
if (!pattern.matcher(eventAttributeValue).matches()) {
return false;
}
} else if (!searchValue.equalsIgnoreCase(eventAttributeValue)) {
return false;
}
} else {
// if FlowFileUUID, search parent & child UUID's also.
if (searchableField.equals(SearchableFields.FlowFileUUID)) {
if (searchValue.contains("?") || searchValue.contains("*")) {
final String regex = searchValue.replace("?", ".").replace("*", ".*");
final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
if (pattern.matcher(event.getFlowFileUuid()).matches()) {
continue;
}
boolean found = false;
for (final String uuid : event.getParentUuids()) {
if (pattern.matcher(uuid).matches()) {
found = true;
break;
}
}
for (final String uuid : event.getChildUuids()) {
if (pattern.matcher(uuid).matches()) {
found = true;
break;
}
}
if (found) {
continue;
}
} else if (event.getFlowFileUuid().equals(searchValue) || event.getParentUuids().contains(searchValue) || event.getChildUuids().contains(searchValue)) {
continue;
}
return false;
}
final Object fieldValue = getFieldValue(event, searchableField);
if (fieldValue == null) {
return false;
}
if (searchValue.contains("?") || searchValue.contains("*")) {
final String regex = searchValue.replace("?", ".").replace("*", ".*");
final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
if (!pattern.matcher(String.valueOf(fieldValue)).matches()) {
return false;
}
} else if (!searchValue.equalsIgnoreCase(String.valueOf(fieldValue))) {
return false;
}
}
}
return true;
}
};
}
private Object getFieldValue(final ProvenanceEventRecord record, final SearchableField field) {
if (SearchableFields.AlternateIdentifierURI.equals(field)) {
return record.getAlternateIdentifierUri();
}
if (SearchableFields.ComponentID.equals(field)) {
return record.getComponentId();
}
if (SearchableFields.Details.equals(field)) {
return record.getDetails();
}
if (SearchableFields.EventTime.equals(field)) {
return record.getEventTime();
}
if (SearchableFields.EventType.equals(field)) {
return record.getEventType();
}
if (SearchableFields.Filename.equals(field)) {
return record.getAttributes().get(CoreAttributes.FILENAME.key());
}
if (SearchableFields.FileSize.equals(field)) {
return record.getFileSize();
}
if (SearchableFields.FlowFileUUID.equals(field)) {
return record.getFlowFileUuid();
}
if (SearchableFields.LineageStartDate.equals(field)) {
return record.getLineageStartDate();
}
if (SearchableFields.Relationship.equals(field)) {
return record.getRelationship();
}
if (SearchableFields.TransitURI.equals(field)) {
return record.getTransitUri();
}
return null;
}
@Override
public QuerySubmission submitQuery(final Query query, final NiFiUser user) {
if (query.getEndDate() != null && query.getStartDate() != null && query.getStartDate().getTime() > query.getEndDate().getTime()) {
throw new IllegalArgumentException("Query End Time cannot be before Query Start Time");
}
if (query.getSearchTerms().isEmpty() && query.getStartDate() == null && query.getEndDate() == null) {
final AsyncQuerySubmission result = new AsyncQuerySubmission(query, 1, user.getIdentity());
queryExecService.submit(new QueryRunnable(ringBuffer, createFilter(query, user), query.getMaxResults(), result));
querySubmissionMap.put(query.getIdentifier(), result);
return result;
}
final AsyncQuerySubmission result = new AsyncQuerySubmission(query, 1, user.getIdentity());
querySubmissionMap.put(query.getIdentifier(), result);
queryExecService.submit(new QueryRunnable(ringBuffer, createFilter(query, user), query.getMaxResults(), result));
return result;
}
@Override
public QuerySubmission retrieveQuerySubmission(final String queryIdentifier, final NiFiUser user) {
final QuerySubmission submission = querySubmissionMap.get(queryIdentifier);
final String userId = submission.getSubmitterIdentity();
if (user == null && userId == null) {
return submission;
}
if (user == null) {
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because no user id was provided in the provenance request.");
}
if (userId == null || userId.equals(user.getIdentity())) {
return submission;
}
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because " + user.getIdentity() + " is not the user who submitted the request.");
}
public Lineage computeLineage(final String flowFileUUID, final NiFiUser user) throws IOException {
return computeLineage(Collections.singleton(flowFileUUID), user, LineageComputationType.FLOWFILE_LINEAGE, null);
}
private Lineage computeLineage(final Collection<String> flowFileUuids, final NiFiUser user, final LineageComputationType computationType, final Long eventId) throws IOException {
final AsyncLineageSubmission submission = submitLineageComputation(flowFileUuids, user, computationType, eventId);
final StandardLineageResult result = submission.getResult();
while (!result.isFinished()) {
try {
Thread.sleep(100L);
} catch (final InterruptedException ie) {
}
}
if (result.getError() != null) {
throw new IOException(result.getError());
}
return new FlowFileLineage(result.getNodes(), result.getEdges());
}
@Override
public ComputeLineageSubmission submitLineageComputation(final long eventId, final NiFiUser user) {
final ProvenanceEventRecord event = getEvent(eventId);
if (event == null) {
final String userId = user.getIdentity();
final AsyncLineageSubmission result = new AsyncLineageSubmission(LineageComputationType.FLOWFILE_LINEAGE, eventId, Collections.emptySet(), 1, userId);
result.getResult().setError("Could not find event with ID " + eventId);
lineageSubmissionMap.put(result.getLineageIdentifier(), result);
return result;
}
return submitLineageComputation(event.getFlowFileUuid(), user);
}
@Override
public AsyncLineageSubmission submitLineageComputation(final String flowFileUuid, final NiFiUser user) {
return submitLineageComputation(Collections.singleton(flowFileUuid), user, LineageComputationType.FLOWFILE_LINEAGE, null);
}
@Override
public ComputeLineageSubmission retrieveLineageSubmission(String lineageIdentifier, final NiFiUser user) {
final ComputeLineageSubmission submission = lineageSubmissionMap.get(lineageIdentifier);
final String userId = submission.getSubmitterIdentity();
if (user == null && userId == null) {
return submission;
}
if (user == null) {
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because no user id was provided in the lineage request.");
}
if (userId == null || userId.equals(user.getIdentity())) {
return submission;
}
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because " + user.getIdentity() + " is not the user who submitted the request.");
}
public Lineage expandSpawnEventParents(String identifier) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public ComputeLineageSubmission submitExpandParents(final long eventId, final NiFiUser user) {
final String userId = user.getIdentity();
final ProvenanceEventRecord event = getEvent(eventId, user);
if (event == null) {
final AsyncLineageSubmission submission = new AsyncLineageSubmission(LineageComputationType.EXPAND_PARENTS, eventId, Collections.emptyList(), 1, userId);
lineageSubmissionMap.put(submission.getLineageIdentifier(), submission);
submission.getResult().update(Collections.emptyList(), 0L);
return submission;
}
switch (event.getEventType()) {
case JOIN:
case FORK:
case REPLAY:
case CLONE:
return submitLineageComputation(event.getParentUuids(), user, LineageComputationType.EXPAND_PARENTS, eventId);
default: {
final AsyncLineageSubmission submission = new AsyncLineageSubmission(LineageComputationType.EXPAND_PARENTS, eventId, Collections.emptyList(), 1, userId);
lineageSubmissionMap.put(submission.getLineageIdentifier(), submission);
submission.getResult().setError("Event ID " + eventId + " indicates an event of type " + event.getEventType() + " so its parents cannot be expanded");
return submission;
}
}
}
public Lineage expandSpawnEventChildren(final String identifier) {
throw new UnsupportedOperationException();
}
@Override
public ComputeLineageSubmission submitExpandChildren(final long eventId, final NiFiUser user) {
final String userId = user.getIdentity();
final ProvenanceEventRecord event = getEvent(eventId, user);
if (event == null) {
final AsyncLineageSubmission submission = new AsyncLineageSubmission(LineageComputationType.EXPAND_CHILDREN, eventId, Collections.emptyList(), 1, userId);
lineageSubmissionMap.put(submission.getLineageIdentifier(), submission);
submission.getResult().update(Collections.emptyList(), 0L);
return submission;
}
switch (event.getEventType()) {
case JOIN:
case FORK:
case REPLAY:
case CLONE:
return submitLineageComputation(event.getChildUuids(), user, LineageComputationType.EXPAND_CHILDREN, eventId);
default: {
final AsyncLineageSubmission submission = new AsyncLineageSubmission(LineageComputationType.EXPAND_CHILDREN, eventId, Collections.emptyList(), 1, userId);
lineageSubmissionMap.put(submission.getLineageIdentifier(), submission);
submission.getResult().setError("Event ID " + eventId + " indicates an event of type " + event.getEventType() + " so its children cannot be expanded");
return submission;
}
}
}
private AsyncLineageSubmission submitLineageComputation(final Collection<String> flowFileUuids, final NiFiUser user, final LineageComputationType computationType, final Long eventId) {
final String userId = user.getIdentity();
final AsyncLineageSubmission result = new AsyncLineageSubmission(computationType, eventId, flowFileUuids, 1, userId);
lineageSubmissionMap.put(result.getLineageIdentifier(), result);
final Filter<ProvenanceEventRecord> filter = new Filter<ProvenanceEventRecord>() {
@Override
public boolean select(final ProvenanceEventRecord event) {
if (user != null && !isAuthorized(event, user)) {
return false;
}
if (flowFileUuids.contains(event.getFlowFileUuid())) {
return true;
}
for (final String parentId : event.getParentUuids()) {
if (flowFileUuids.contains(parentId)) {
return true;
}
}
for (final String childId : event.getChildUuids()) {
if (flowFileUuids.contains(childId)) {
return true;
}
}
return false;
}
};
queryExecService.submit(new ComputeLineageRunnable(ringBuffer, filter, result));
return result;
}
private static class QueryRunnable implements Runnable {
private final RingBuffer<ProvenanceEventRecord> ringBuffer;
private final Filter<ProvenanceEventRecord> filter;
private final AsyncQuerySubmission submission;
private final int maxRecords;
public QueryRunnable(final RingBuffer<ProvenanceEventRecord> ringBuffer, final Filter<ProvenanceEventRecord> filter, final int maxRecords, final AsyncQuerySubmission submission) {
this.ringBuffer = ringBuffer;
this.filter = filter;
this.submission = submission;
this.maxRecords = maxRecords;
}
@Override
public void run() {
// Retrieve the most recent results and count the total number of matches
final AtomicInteger matchingCount = new AtomicInteger(0);
final List<ProvenanceEventRecord> matchingRecords = new ArrayList<>(maxRecords);
ringBuffer.forEach(new ForEachEvaluator<ProvenanceEventRecord>() {
@Override
public boolean evaluate(final ProvenanceEventRecord record) {
if (filter.select(record)) {
if (matchingCount.incrementAndGet() <= maxRecords) {
matchingRecords.add(record);
}
}
return true;
}
}, IterationDirection.BACKWARD);
submission.getResult().update(matchingRecords, matchingCount.get());
}
}
private static class ComputeLineageRunnable implements Runnable {
private final RingBuffer<ProvenanceEventRecord> ringBuffer;
private final Filter<ProvenanceEventRecord> filter;
private final AsyncLineageSubmission submission;
public ComputeLineageRunnable(final RingBuffer<ProvenanceEventRecord> ringBuffer, final Filter<ProvenanceEventRecord> filter, final AsyncLineageSubmission submission) {
this.ringBuffer = ringBuffer;
this.filter = filter;
this.submission = submission;
}
@Override
public void run() {
final List<ProvenanceEventRecord> records = ringBuffer.getSelectedElements(filter);
submission.getResult().update(records, records.size());
}
}
private class RemoveExpiredQueryResults implements Runnable {
@Override
public void run() {
final Date now = new Date();
final Iterator<Map.Entry<String, AsyncQuerySubmission>> queryIterator = querySubmissionMap.entrySet().iterator();
while (queryIterator.hasNext()) {
final Map.Entry<String, AsyncQuerySubmission> entry = queryIterator.next();
final StandardQueryResult result = entry.getValue().getResult();
if (result.isFinished() && result.getExpiration().before(now)) {
querySubmissionMap.remove(entry.getKey());
}
}
final Iterator<Map.Entry<String, AsyncLineageSubmission>> lineageIterator = lineageSubmissionMap.entrySet().iterator();
while (lineageIterator.hasNext()) {
final Map.Entry<String, AsyncLineageSubmission> entry = lineageIterator.next();
final StandardLineageResult result = entry.getValue().getResult();
if (result.isFinished() && result.getExpiration().before(now)) {
querySubmissionMap.remove(entry.getKey());
}
}
}
}
private static class IdEnrichedProvEvent implements ProvenanceEventRecord {
private final ProvenanceEventRecord record;
private final long id;
public IdEnrichedProvEvent(final ProvenanceEventRecord record, final long id) {
this.record = record;
this.id = id;
}
@Override
public long getEventId() {
return id;
}
@Override
public long getEventTime() {
return record.getEventTime();
}
@Override
public long getFlowFileEntryDate() {
return record.getFlowFileEntryDate();
}
@Override
public long getLineageStartDate() {
return record.getLineageStartDate();
}
@Override
public long getFileSize() {
return record.getFileSize();
}
@Override
public Long getPreviousFileSize() {
return record.getPreviousFileSize();
}
@Override
public long getEventDuration() {
return record.getEventDuration();
}
@Override
public ProvenanceEventType getEventType() {
return record.getEventType();
}
@Override
public Map<String, String> getAttributes() {
return record.getAttributes();
}
@Override
public Map<String, String> getPreviousAttributes() {
return record.getPreviousAttributes();
}
@Override
public Map<String, String> getUpdatedAttributes() {
return record.getUpdatedAttributes();
}
@Override
public String getComponentId() {
return record.getComponentId();
}
@Override
public String getComponentType() {
return record.getComponentType();
}
@Override
public String getTransitUri() {
return record.getTransitUri();
}
@Override
public String getSourceSystemFlowFileIdentifier() {
return record.getSourceSystemFlowFileIdentifier();
}
@Override
public String getFlowFileUuid() {
return record.getFlowFileUuid();
}
@Override
public List<String> getParentUuids() {
return record.getParentUuids();
}
@Override
public List<String> getChildUuids() {
return record.getChildUuids();
}
@Override
public String getAlternateIdentifierUri() {
return record.getAlternateIdentifierUri();
}
@Override
public String getDetails() {
return record.getDetails();
}
@Override
public String getRelationship() {
return record.getRelationship();
}
@Override
public String getSourceQueueIdentifier() {
return record.getSourceQueueIdentifier();
}
@Override
public String getContentClaimSection() {
return record.getContentClaimSection();
}
@Override
public String getPreviousContentClaimSection() {
return record.getPreviousContentClaimSection();
}
@Override
public String getContentClaimContainer() {
return record.getContentClaimContainer();
}
@Override
public String getPreviousContentClaimContainer() {
return record.getPreviousContentClaimContainer();
}
@Override
public String getContentClaimIdentifier() {
return record.getContentClaimIdentifier();
}
@Override
public String getPreviousContentClaimIdentifier() {
return record.getPreviousContentClaimIdentifier();
}
@Override
public Long getContentClaimOffset() {
return record.getContentClaimOffset();
}
@Override
public Long getPreviousContentClaimOffset() {
return record.getPreviousContentClaimOffset();
}
/**
* Returns the best event identifier for this event (eventId if available, descriptive identifier if not yet persisted to allow for traceability).
*
* @return a descriptive event ID to allow tracing
*/
@Override
public String getBestEventIdentifier() {
return Long.toString(getEventId());
}
}
}