package com.linkedin.databus2.core.seq; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * 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. * */ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Date; import java.util.concurrent.atomic.AtomicLong; import org.apache.log4j.Logger; import com.linkedin.databus.core.util.ConfigBuilder; import com.linkedin.databus.core.util.InvalidConfigException; import com.linkedin.databus2.core.DatabusException; /** * This class reads and saves max SCN in a text file. * * @author abhasin */ public class FileMaxSCNHandler implements MaxSCNReaderWriter { private static final String TEMP = ".temp"; public static final String SCN_SEPARATOR = ":"; private static final String MODULE = FileMaxSCNHandler.class.getName(); private static final Logger LOG = Logger.getLogger(MODULE); //private final String _key; //private final String _scnDir; private final String _scnFileName; //private final Long _flushInterval; //private final Long _defaultInitVal; private final AtomicLong _scn; private final AtomicLong _flushCounter; private final StaticConfig _staticConfig; /** * Factory method for dependency injection * * @param config the MaxSCN handler configuration builder * @return a new handler object */ public static FileMaxSCNHandler create(Config config) throws IOException, DatabusException, InvalidConfigException { return create(config.build()); } /** * Factory method for dependency injection * * @param config the MaxSCN handler static configuration * @return a new handler object */ public static FileMaxSCNHandler create(StaticConfig config) throws IOException, DatabusException { FileMaxSCNHandler hdlr = new FileMaxSCNHandler(config); hdlr.loadInitialValue(); return hdlr; } public FileMaxSCNHandler(StaticConfig config) { _staticConfig = config; _flushCounter = new AtomicLong(0); _scn = new AtomicLong(0); _scnFileName = _staticConfig.getScnDir().getAbsolutePath() + File.separator + _staticConfig.getKey(); LOG.info("creating file:" + _scnFileName); } /** * Reads scn value from SCN File name according to configuration. If SCN file does not * exist, it creates the SCN file w/the initial value specified in the configuration. An * exception is thrown if we the SCN file exists but we fail to read the SCN value from * it. * * @throws IOException * if we fail to open or read from the SCN file * @throws RuntimeException * if the SCN value cannot be read or parsed from the file */ protected void loadInitialValue() throws IOException, DatabusException { Long initVal = null; LOG.info("Trying to read initial SCN from file: " + _scnFileName); File file = new File(_scnFileName); if (file.exists()) { FileReader fileReader = new FileReader(file); try { BufferedReader reader = new BufferedReader(fileReader); String scnLine = reader.readLine(); if (null != scnLine) { try { String scnString = scnLine.substring(0, scnLine.indexOf(SCN_SEPARATOR)); _scn.set(Long.parseLong(scnString)); LOG.info("Starting from MAX SCN:" + scnString); } catch (Exception e) { LOG.error("Could not read initial SCN value. Value missing or not in expected format; scnLine = " + scnLine, e); throw new DatabusException("Failed to load initial SCN value. Value misisng or not in expected format.", e); } } else { LOG.warn("SCN file empty; defaulting to initial value from configuration:" + _staticConfig.getInitVal()); _scn.set(_staticConfig.getInitVal()); } reader.close(); } finally { fileReader.close(); } } else { LOG.info("Initial max SCN does not exist. Defaulting to initial value from configuration: " + _staticConfig.getInitVal()); _scn.set(_staticConfig.getInitVal()); writeScnToFile(); } } /** * Write SCN value to file. If SCN file exists, move it aside and create a new file * w/new value. */ private void writeScnToFile() throws IOException { long scn = _scn.longValue(); File dir = _staticConfig.getScnDir(); if (! dir.exists() && !dir.mkdirs()) { throw new IOException("unable to create SCN file parent:" + dir.getAbsolutePath()); } // delete the temp file if one exists File tempScnFile = new File(_scnFileName + TEMP); if (tempScnFile.exists() && !tempScnFile.delete()) { LOG.error("unable to erase temp SCN file: " + tempScnFile.getAbsolutePath()); } File scnFile = new File(_scnFileName); if (scnFile.exists() && !scnFile.renameTo(tempScnFile)) { LOG.error("unable to backup scn file"); } if (!scnFile.createNewFile()) { LOG.error("unable to create new SCN file:" + scnFile.getAbsolutePath()); } FileWriter writer = new FileWriter(scnFile); writer.write(Long.toString(scn)); writer.write(SCN_SEPARATOR + new Date().toString()); writer.flush(); writer.close(); LOG.debug("scn persisted: " + scn); } @Override public long getMaxScn() { return _scn.get(); } @Override public void saveMaxScn(long endOfPeriod) throws DatabusException { _scn.set(endOfPeriod); long ctr = _flushCounter.addAndGet(1); // Retain the SCN Val every now and then if (ctr % _staticConfig.getFlushItvl() == 0) { if (LOG.isDebugEnabled()) { LOG.debug("Flushing counter:" + ctr); } try { writeScnToFile(); } catch (IOException e) { LOG.error("Caught exception saving SCN = " + _scn, e); throw new DatabusException("Caught exception saving SCN = " + _scn, e); } } } /** * Make Sure We dump back the SCN VAL on shut down * */ public void destroy() { LOG.info("destory() called, saving scn file before shutting down."); try { writeScnToFile(); } catch (IOException e) { LOG.error("Failed to write final SCN value to file on destroy()", e); } } @Override public String toString() { return _scnFileName + ":" + _scn; } public static class StaticConfig { private final String _key; private final File _scnDir; private final Long _initVal; private final Long _flushItvl; public StaticConfig(String key, File scnDir, Long initVal, Long flushItvl) { super(); _key = key; _scnDir = scnDir; _initVal = initVal; _flushItvl = flushItvl; } /** the name of the file used for storing the SCN */ public String getKey() { return _key; } /** the directory where the SCN will be saved */ public File getScnDir() { return _scnDir; } /** the initial scn value */ public Long getInitVal() { return _initVal; } /** the number of SCN updates before the SCN is persisted to disk */ public Long getFlushItvl() { return _flushItvl; } } public static class Config implements ConfigBuilder<StaticConfig> { private String _key = "MaxSCN"; private String _scnDir = "databus2-maxscn"; private Long _initVal = 0L; private Long _flushItvl = 1L; /** the name of the file used for storing the SCN */ public String getKey() { return _key; } /** Changes the name of the file used for storing the SCN */ public void setKey(String key) { this._key = key; } /** Changes the directory where the SCN will be saved */ public void setScnDir(String scnDir) { _scnDir = scnDir; } /** Returns the directory where the SCN will be saved */ public String getScnDir() { return _scnDir; } /** Sets the initial scn value */ public void setInitVal(long scn) { _initVal = scn; } /** Returns the initial scn value */ public long getInitVal() { return _initVal; } /** Sets the number of SCN updates before the SCN is persisted to disk */ public void setFlushItvl(Long flushItvl) { _flushItvl = flushItvl; } /** Returns the number of SCN updates before the SCN is persisted to disk */ public Long getFlushItvl() { return _flushItvl; } @Override public StaticConfig build() throws InvalidConfigException { File scnDir = new File(_scnDir); if (!scnDir.exists()) { if (!scnDir.mkdirs()) throw new InvalidConfigException("Unable to create scn dir:" + _scnDir); } if (! scnDir.isDirectory()) throw new InvalidConfigException("Not an scn dir:" + _scnDir); if (_flushItvl <= 0) throw new InvalidConfigException("Invalid flush interval:" + _flushItvl); return new StaticConfig(_key, scnDir, _initVal, _flushItvl); } } }