/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.notifications.impl;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hazelcast.core.MapLoader;
import com.hazelcast.core.MapStore;
/**
* Hazelcast persistence provider implementation of @MapLoader and @MapStore to serialize and
* persist Java objects stored in Hazelcast cache to disk.
*
*/
public class FileSystemPersistenceProvider
implements MapLoader<String, Object>, MapStore<String, Object> {
private static final Logger LOGGER = LoggerFactory
.getLogger(FileSystemPersistenceProvider.class);
private static final String PERSISTED_FILE_SUFFIX = ".ser";
private static final String PERSISTED_FILE_SUFFIX_REGEX = "\\.ser";
private String mapName = "default";
private FilenameFilter filter;
public FileSystemPersistenceProvider() {
}
public FileSystemPersistenceProvider(String mapName) {
LOGGER.debug("INSIDE: FileSystemPersistenceProvider constructor, mapName = {}", mapName);
this.mapName = mapName;
File dir = new File(getPersistencePath());
if (!dir.exists()) {
if (!dir.mkdir()) {
LOGGER.warn("Unable to create directory: {}", dir.getAbsolutePath());
}
}
}
/**
* Retrieve root directory of all persisted Hazelcast objects for this cache. The path is
* relative to containing bundle, i.e., DDF install directory.
*
* @return the path to root directory where serialized objects will be persisted
*/
String getPersistencePath() {
return "data/";
}
/**
* Path to where persisted Hazelcast objects will be stored to disk.
*
* @return
*/
String getMapStorePath() {
return getPersistencePath() + mapName + "/";
}
@Override
public void store(String key, Object value) {
OutputStream file = null;
ObjectOutputStream output = null;
try {
File dir = new File(getMapStorePath());
if (!dir.exists()) {
if (!dir.mkdir()) {
LOGGER.warn("Unable to create directory: {}", dir.getAbsolutePath());
}
}
file = new FileOutputStream(getMapStorePath() + key + PERSISTED_FILE_SUFFIX);
OutputStream buffer = new BufferedOutputStream(file);
output = new ObjectOutputStream(buffer);
output.writeObject(value);
} catch (IOException e) {
LOGGER.info("IOException storing value in cache with key = " + key, e);
} finally {
IOUtils.closeQuietly(output);
IOUtils.closeQuietly(file);
}
}
@Override
public void storeAll(Map<String, Object> keyValueMap) {
for (Map.Entry<String, Object> entry : keyValueMap.entrySet()) {
store(entry.getKey(), entry.getValue());
}
}
@Override
public void delete(String key) {
File file = new File(getMapStorePath() + key + PERSISTED_FILE_SUFFIX);
if (file.exists()) {
if (!file.delete()) {
LOGGER.warn("File was unable to be deleted: {}", file.getAbsolutePath());
}
}
}
@Override
public void deleteAll(Collection<String> keys) {
for (String key : keys) {
delete(key);
}
}
@Override
public Object load(String key) {
// Not implemented because the Hazelcast data grid is all in cache,
// so never have something persisted that is
// not in memory and want to avoid a performance hit on the file system
return null;
}
Object loadFromPersistence(String key) {
File file = new File(getMapStorePath() + key + PERSISTED_FILE_SUFFIX);
if (!file.exists()) {
return null;
}
InputStream inputStream = null;
try {
inputStream = new FileInputStream(getMapStorePath() + key + PERSISTED_FILE_SUFFIX);
InputStream buffer = new BufferedInputStream(inputStream);
ObjectInput input = new ObjectInputStream(buffer);
return (Object) input.readObject();
} catch (IOException e) {
LOGGER.debug("IOException", e);
} catch (ClassNotFoundException e) {
LOGGER.debug("ClassNotFoundException", e);
} finally {
IOUtils.closeQuietly(inputStream);
}
return null;
}
@Override
public Map<String, Object> loadAll(Collection<String> keys) {
Map<String, Object> values = new HashMap<String, Object>();
for (String key : keys) {
Object obj = loadFromPersistence(key);
if (obj != null) {
values.put(key, obj);
}
}
return values;
}
private FilenameFilter getFilenameFilter() {
if (filter == null) {
filter = new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.toLowerCase().endsWith(PERSISTED_FILE_SUFFIX);
}
};
}
return filter;
}
@Override
public Set<String> loadAllKeys() {
Set<String> keys = new HashSet<String>();
File[] files = new File(getMapStorePath()).listFiles(getFilenameFilter());
if (files == null) {
return keys;
}
for (File file : files) {
keys.add(file.getName().replaceFirst(PERSISTED_FILE_SUFFIX_REGEX, ""));
}
return keys;
}
public void clear() {
File[] files = new File(getMapStorePath()).listFiles(getFilenameFilter());
if (files != null) {
for (File file : files) {
if (!file.delete()) {
LOGGER.warn("File was unable to be deleted: {}", file.getAbsolutePath());
}
}
}
}
}