/** * Copyright 2016 StreamSets Inc. * * Licensed under 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 com.streamsets.pipeline.lib.io; import com.streamsets.pipeline.api.impl.Utils; import com.streamsets.pipeline.lib.util.FileContextProviderUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; public class BaseFileContextProvider implements FileContextProvider{ private static final Logger LOG = LoggerFactory.getLogger(BaseFileContextProvider.class); protected List<FileContext> fileContexts; private int startingIdx; private int currentIdx; private int loopIdx; BaseFileContextProvider() { startingIdx = 0; } /** * Sets the file offsets to use for the next read. To work correctly, the last return offsets should be used or * an empty <code>Map</code> if there is none. * <p/> * If a reader is already live, the corresponding set offset is ignored as we cache all the contextual information * of live readers. * * @param offsets directory offsets. * @throws IOException thrown if there was an IO error while preparing file offsets. */ @Override public void setOffsets(Map<String, String> offsets) throws IOException{ Utils.checkNotNull(offsets, "offsets"); // retrieve file:offset for each directory for (FileContext fileContext : fileContexts) { String offset = offsets.get(fileContext.getMultiFileInfo().getFileKey()); LiveFile file = null; long fileOffset = 0; if (offset != null && !offset.isEmpty()) { file = FileContextProviderUtil.getRefreshedLiveFileFromFileOffset(offset); fileOffset = FileContextProviderUtil.getLongOffsetFromFileOffset(offset); } fileContext.setStartingCurrentFileName(file); fileContext.setStartingOffset(fileOffset); if (LOG.isTraceEnabled()) { LOG.trace("Setting offset: directory '{}', file '{}', offset '{}'", fileContext.getMultiFileInfo().getFileFullPath(), file, fileOffset); } } currentIdx = startingIdx; } @Override public Map<String, Long> getOffsetsLag(Map<String, String> offsetMap) throws IOException { Map<String, Long> offsetLagMap = new HashMap<String, Long>(); for (FileContext fileContext : fileContexts) { String fileKey = fileContext.getMultiFileInfo().getFileKey(); if (fileContext.hasReader()) { //Whatever does not have offsets (i.e empty offsets) mean their live (current) file is null //they will appear in pending files. if (offsetMap.containsKey(fileKey) && !offsetMap.get(fileKey).isEmpty()) { offsetLagMap.put(fileKey, FileContextProviderUtil.getOffsetLagForFile(offsetMap.get(fileKey))); } } } return offsetLagMap; } @Override public Map<String, Long> getPendingFiles() throws IOException{ Map<String, Long> pendingFiles = new HashMap<String, Long>(); for (FileContext context : fileContexts) { pendingFiles.put(context.getMultiFileInfo().getFileKey(), context.getPendingFiles()); } return pendingFiles; } @Override public Map<String, String> getOffsets() throws IOException { Map<String, String> map = new HashMap<>(); // produce file:offset for each directory taking into account a current reader and its file state. for (FileContext fileContext : fileContexts) { LiveFile file; long fileOffset; if (!fileContext.hasReader()) { file = fileContext.getStartingCurrentFileName(); fileOffset = fileContext.getStartingOffset(); } else if (fileContext.getReader().hasNext()) { file = fileContext.getReader().getLiveFile(); fileOffset = fileContext.getReader().getOffset(); } else { file = fileContext.getReader().getLiveFile(); fileOffset = Long.MAX_VALUE; } String offset = (file == null) ? "" : FileContextProviderUtil.createFileOffsetString(fileOffset, file); map.put(fileContext.getMultiFileInfo().getFileKey(), offset); if (LOG.isTraceEnabled()) { LOG.trace("Reporting offset: directory '{}', pattern: '{}', file '{}', offset '{}'", fileContext.getMultiFileInfo().getFileFullPath(), fileContext.getRollMode().getPattern(), file, fileOffset ); } } startingIdx = getAndIncrementIdx(); loopIdx = 0; return map; } @Override public FileContext next() { loopIdx++; return fileContexts.get(getAndIncrementIdx()); } @Override public boolean didFullLoop() { return loopIdx >= fileContexts.size(); } @Override public void startNewLoop() { LOG.trace("startNewLoop()"); loopIdx = 0; } @Override public void close() { for (FileContext fileContext : fileContexts) { fileContext.close(); } } @Override public void purge() { //NOP } private int getAndIncrementIdx() { int idx = currentIdx; incrementIdx(); return idx; } private int incrementIdx() { currentIdx = (fileContexts.isEmpty()) ? 0 : (currentIdx + 1) % fileContexts.size(); return currentIdx; } protected void resetCurrentAndStartingIdx() { currentIdx = 0; startingIdx = 0; } }