package eu.dnetlib.iis.common.cache; import static eu.dnetlib.iis.common.WorkflowRuntimeParameters.OOZIE_ACTION_OUTPUT_FILENAME; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.Map; import java.util.Properties; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import com.google.gson.Gson; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.google.gson.stream.JsonWriter; import eu.dnetlib.iis.common.FsShellPermissions; import eu.dnetlib.iis.common.java.PortBindings; import eu.dnetlib.iis.common.java.porttype.PortType; /** * CacheMetadata managing process. * * @author mhorst * */ public class CacheMetadataManagingProcess implements eu.dnetlib.iis.common.java.Process { public static final String OUTPUT_PROPERTY_CACHE_ID = "cache_id"; public static final String PARAM_CACHE_DIR = "cache_location"; public static final String PARAM_MODE = "mode"; public static final String PARAM_ID = "id"; public static final String MODE_READ_CURRENT_ID = "read_current_id"; public static final String MODE_GENERATE_NEW_ID = "generate_new_id"; public static final String MODE_WRITE_ID = "write_id"; public static final String DEFAULT_METAFILE_NAME = "meta.json"; public static final int CACHE_ID_PADDING_LENGTH = 6; public static final String NON_EXISTING_CACHE_ID = "$UNDEFINED$"; public static final String DEFAULT_ENCODING = "UTF-8"; public class CacheMeta { private String currentCacheId; public String getCurrentCacheId() { return currentCacheId; } public void setCurrentCacheId(String currentCacheId) { this.currentCacheId = currentCacheId; } } /** * Underlying file system facade factory. */ private final FileSystemFacadeFactory fsFacadeFactory; // ---------------------------- CONSTRUCTORS -------------------------- /** * Default constructor instantiating process with hadoop file system. */ public CacheMetadataManagingProcess() { this((conf) -> { return new HadoopFileSystemFacade(FileSystem.get(conf)); }); } /** * Constructor instantiating process with custom file system facade. */ public CacheMetadataManagingProcess(FileSystemFacadeFactory fsFacadeFactory) { this.fsFacadeFactory = fsFacadeFactory; } // ---------------------------- LOGIC --------------------------------- @Override public Map<String, PortType> getInputPorts() { return Collections.emptyMap(); } @Override public Map<String, PortType> getOutputPorts() { return Collections.emptyMap(); } @Override public void run(PortBindings portBindings, Configuration conf, Map<String, String> parameters) throws Exception { String mode = parameters.get(PARAM_MODE); Properties props = new Properties(); if (MODE_READ_CURRENT_ID.equals(mode)) { props.setProperty(OUTPUT_PROPERTY_CACHE_ID, getExistingCacheId(conf, parameters)); } else if (MODE_GENERATE_NEW_ID.equals(mode)) { props.setProperty(OUTPUT_PROPERTY_CACHE_ID, generateNewCacheId(conf, parameters)); } else if (MODE_WRITE_ID.equals(mode)) { writeCacheId(conf, parameters); } else { throw new RuntimeException("unsupported mode: " + mode); } File file = new File(System.getProperty(OOZIE_ACTION_OUTPUT_FILENAME)); OutputStream os = new FileOutputStream(file); try { props.store(os, ""); } finally { os.close(); } } // ---------------------------- PRIVATE --------------------------------- private String getExistingCacheId(Configuration conf, Map<String, String> parameters) throws IOException { if (parameters.containsKey(PARAM_CACHE_DIR)) { CacheMeta cacheMeta = readCacheMeta(fsFacadeFactory.create(conf), new Path(parameters.get(PARAM_CACHE_DIR), DEFAULT_METAFILE_NAME)); if (cacheMeta != null) { return cacheMeta.getCurrentCacheId(); } else { return NON_EXISTING_CACHE_ID; } } else { throw new RuntimeException("cache directory location not provided! " + "'" + PARAM_CACHE_DIR + "' parameter is missing!"); } } private String generateNewCacheId(Configuration conf, Map<String, String> parameters) throws IOException { if (parameters.containsKey(PARAM_CACHE_DIR)) { CacheMeta cachedMeta = readCacheMeta(fsFacadeFactory.create(conf), new Path(parameters.get(PARAM_CACHE_DIR), DEFAULT_METAFILE_NAME)); if (cachedMeta != null) { int currentIndex = convertCacheIdToInt(cachedMeta.getCurrentCacheId()); return convertIntToCacheId(currentIndex+1); } else { // initializing cache meta return convertIntToCacheId(1); } } else { throw new RuntimeException("cache directory location not provided! " + "'" + PARAM_CACHE_DIR + "' parameter is missing!"); } } private void writeCacheId(Configuration conf, Map<String, String> parameters) throws IOException { if (parameters.containsKey(PARAM_CACHE_DIR)) { if (parameters.containsKey(PARAM_ID)) { FileSystemFacade fs = fsFacadeFactory.create(conf); Path cacheFilePath = new Path(parameters.get(PARAM_CACHE_DIR), DEFAULT_METAFILE_NAME); CacheMeta cachedMeta = getCacheMeta(parameters.get(PARAM_ID), fs, cacheFilePath); Gson gson = new Gson(); OutputStream outputStream = fs.create(cacheFilePath, true); JsonWriter writer = new JsonWriter(new OutputStreamWriter(outputStream, DEFAULT_ENCODING)); try { gson.toJson(cachedMeta, CacheMeta.class, writer); } finally { writer.close(); outputStream.close(); // changing file permission to +rw to allow writing for different users fs.changePermissions(conf, FsShellPermissions.Op.CHMOD, false, "0666", cacheFilePath.toString()); } } else { throw new RuntimeException("unable to write new cache id in meta.json file, " + "no '" + PARAM_ID + "' input parameter provied!"); } } else { throw new RuntimeException("cache directory location not provided! " + "'" + PARAM_CACHE_DIR + "' parameter is missing!"); } } /** * Reads or creates new cache metadata record. */ private CacheMeta getCacheMeta(String cacheId, FileSystemFacade fs, Path cacheFilePath) throws JsonSyntaxException, JsonIOException, UnsupportedEncodingException, IOException { CacheMeta cachedMeta = readCacheMeta(fs, cacheFilePath); // writing new id if (cachedMeta==null) { cachedMeta = new CacheMeta(); } cachedMeta.setCurrentCacheId(cacheId); return cachedMeta; } private CacheMeta readCacheMeta(FileSystemFacade fs, Path cacheFilePath) throws JsonSyntaxException, JsonIOException, UnsupportedEncodingException, IOException { if (fs.exists(cacheFilePath)) { InputStream inputStream = fs.open(cacheFilePath); InputStreamReader reader = new InputStreamReader( inputStream, DEFAULT_ENCODING); try { Gson gson = new Gson(); return gson.fromJson(reader, CacheMeta.class); } finally { reader.close(); inputStream.close(); } } else { return null; } } private static int convertCacheIdToInt(String cacheId) { StringBuffer strBuff = new StringBuffer(cacheId); while (true) { if (strBuff.charAt(0)=='0') { strBuff.deleteCharAt(0); } else { break; } } return Integer.parseInt(strBuff.toString()); } private static String convertIntToCacheId(int cacheIndex) { StringBuffer strBuff = new StringBuffer(String.valueOf(cacheIndex)); while(strBuff.length()<CACHE_ID_PADDING_LENGTH) { strBuff.insert(0, '0'); } return strBuff.toString(); } }