/*******************************************************************************
* Copyright © 2012-2015 eBay Software Foundation
* This program is dual licensed under the MIT and Apache 2.0 licenses.
* Please see LICENSE for more information.
*******************************************************************************/
package com.ebay.jetstream.event.channel.file;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ebay.jetstream.config.ConfigUtils;
import com.ebay.jetstream.event.EventException;
/**
* This class was and should be part of FileChannelAddress or a base class of OutboudFileChannel
* if we follow "expert" pattern. However,
* to make the "address" simple, we move some implementation
* details from the "address" class into this "helper" class.
*
* Note: one outboundfilechannelAddress is 1-1 mapping to one such helper!
*
* This class will not do any sanity checking which is performed
* by the "address" class already.
*
* @author gjin
*/
public class OutboundFileChannelHelper {
private FileChannelAddress m_address;
private FileOutputStream m_fos = null;
private AtomicLong m_totalWrites = new AtomicLong();
private AtomicBoolean m_reportFull = new AtomicBoolean();
private AtomicLong m_errorLoggingRate = new AtomicLong(5000); /* log every 5000 errors initially*/
private AtomicLong m_numberOfErrors = new AtomicLong();
private static final Logger LOGGER = LoggerFactory.getLogger("com.ebay.jetstream.event.channel.file");
/***
*
*/
public OutboundFileChannelHelper(FileChannelAddress address) {
m_address = address;
if (m_address == null) {
throw new IllegalArgumentException("the channel address must be no null");
}
}
public FileChannelAddress getAddress() {
return m_address;
}
public void closeForWrite() throws EventException {
if (m_fos == null) {
LOGGER.info( "try to close an event file " + m_address.getPathname() + " which was not open", "CloseUnOpenFile");
return; /* close already or never open*/
}
try {
m_fos.flush();
m_fos.close();
} catch (IOException e) {
throw new EventException("close file: " + m_address.getPathname(), e);
}
m_fos = null;
}
/***
* open the file with appending mode. if the file exists already, a warning log is issued.
* if failed to create or open the file, a error log is issued.
*/
public void openForWrite() throws EventException {
File f = new File(ConfigUtils.getInitialPropertyExpanded(m_address.getPathname()));
try {
if (!f.exists()) {
f.createNewFile();
} else {
LOGGER.info( "appending to file=" + m_address.getPathname());
}
} catch (IOException ioe) {
throw new EventException("failed to create file=" + m_address.getPathname() + ", e=" + ioe);
}
try {
/*** append mode ***/
m_fos = new FileOutputStream(f, true);
} catch (FileNotFoundException fnfe) {
throw new EventException("failed to open file=" + m_address.getPathname() + ", e=" + fnfe);
}
}
/**
* if the file is full, we report the WARNING and close the file
* @param lineEvent
* @return true if write is a success
*/
public boolean writeEvent(String lineEvent) {
if (m_fos == null) {
return false;
}
if (lineEvent == null ) {
return false;
}
if (m_totalWrites.get() >= m_address.getMaxNumberOfRecords()) {
/* should we not sync here to save some performance. as a result we may see a few similar errors at the same time */
if (!m_reportFull.getAndSet(true)) {
LOGGER.warn( "file=" + m_address.getPathname() + " reaches the max number of records =" +
m_address.getMaxNumberOfRecords() + " in this session, no more write!");
closeForWrite();
}
return false;
}
try {
m_fos.write(lineEvent.getBytes());
m_fos.flush();
} catch (IOException e) {
if (m_numberOfErrors.getAndIncrement() % m_errorLoggingRate.get() ==0) {
LOGGER.error( "failed to write file=" + m_address.getPathname() + ", e=" + e);
}
return false;
}
m_totalWrites.getAndIncrement();
//System.out.println("id=" + Thread.currentThread().getId() + ", total=" + m_totalWrites.get());
return true;
}
}