/**
* 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.catalog.plugin.metacard.backup.storage.filestorage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.codice.ddf.catalog.plugin.metacard.backup.storage.internal.MetacardBackupException;
import org.codice.ddf.catalog.plugin.metacard.backup.storage.internal.MetacardBackupStorageProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MetacardBackupFileStorage implements MetacardBackupStorageProvider {
private static final String DESCRIBABLE_PROPERTIES_FILE = "/describable.properties";
private static final String DESCRIPTION_PROPERTY = "description";
private static final String ORGANIZATION_PROPERTY = "organization";
private static final String VERSION_PROPERTY = "version";
private static final String TITLE_PROPERTY = "name";
private static final String ID_PROPERTY = "id";
private static final String OUTPUT_DIRECTORY_PROPERTY = "outputDirectory";
private static final Logger LOGGER = LoggerFactory.getLogger(MetacardBackupFileStorage.class);
private String id;
private String outputDirectory;
private static Properties describableProperties = new Properties();
static {
try (InputStream properties = MetacardBackupFileStorage.class.getResourceAsStream(
DESCRIBABLE_PROPERTIES_FILE)) {
describableProperties.load(properties);
} catch (IOException e) {
LOGGER.debug("Unable to load describable properties", e);
}
}
@Override
public String getId() {
return id;
}
@Override
public String getVersion() {
return (String) describableProperties.get(VERSION_PROPERTY);
}
@Override
public String getTitle() {
return (String) describableProperties.get(TITLE_PROPERTY);
}
@Override
public String getDescription() {
return (String) describableProperties.get(DESCRIPTION_PROPERTY);
}
@Override
public String getOrganization() {
return (String) describableProperties.get(ORGANIZATION_PROPERTY);
}
public void setId(String id) {
this.id = id;
}
@Override
public void delete(String id) throws IOException, MetacardBackupException {
if (StringUtils.isEmpty(outputDirectory)) {
throw new MetacardBackupException(
"Unable to delete stored data; no output directory specified.");
}
deleteBackupIfPresent(id);
}
@Override
public void store(String id, byte[] data) throws IOException, MetacardBackupException {
if (StringUtils.isEmpty(outputDirectory)) {
throw new MetacardBackupException("Unable to store data; no output directory specified.");
}
if (data == null) {
throw new MetacardBackupException("No data to store");
}
Path metacardPath = getMetacardDirectory(id);
if (metacardPath == null) {
String message = String.format("Unable to create metacard path directory for %s", id);
LOGGER.debug(message);
throw new MetacardBackupException(message);
}
try {
Path parent = metacardPath.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Files.createFile(metacardPath);
} catch (IOException e) {
LOGGER.trace("Unable to create empty backup file {}. File may already exist.",
metacardPath,
e);
}
try (OutputStream outputStream = new FileOutputStream(metacardPath.toFile())) {
IOUtils.write(data, outputStream);
}
}
public void setOutputDirectory(String outputDirectory) {
this.outputDirectory = outputDirectory;
}
public String getOutputDirectory() {
return outputDirectory;
}
public void refresh(Map<String, Object> properties) {
Object storedOutputDirectory = properties.get(OUTPUT_DIRECTORY_PROPERTY);
if (storedOutputDirectory instanceof String
&& StringUtils.isNotBlank((String) storedOutputDirectory)) {
this.outputDirectory = (String) storedOutputDirectory;
}
Object storedId = properties.get(ID_PROPERTY);
if (storedId instanceof String) {
setId((String) storedId);
}
}
private void deleteBackupIfPresent(String filename) throws MetacardBackupException {
Path metacardPath = getMetacardDirectory(filename);
if (metacardPath == null) {
LOGGER.debug("Unable to delete backup for: {}", filename);
throw new MetacardBackupException("Unable to delete backup");
}
try {
Files.deleteIfExists(metacardPath);
while (metacardPath.getParent() != null && !metacardPath.getParent()
.toString()
.equals(outputDirectory)) {
metacardPath = metacardPath.getParent();
if (isDirectoryEmpty(metacardPath)) {
FileUtils.deleteDirectory(metacardPath.toFile());
}
}
} catch (IOException e) {
LOGGER.debug("Unable to delete backup file {}", metacardPath, e);
throw new MetacardBackupException("Unable to delete backup file", e);
}
}
public Path getMetacardDirectory(String id) {
if (StringUtils.isEmpty(id)) {
return null;
}
if (id.length() < 6) {
id = StringUtils.rightPad(id, 6, "0");
}
try {
return Paths.get(outputDirectory, id.substring(0, 3), id.substring(3, 6), id);
} catch (InvalidPathException e) {
LOGGER.debug("Unable to create path from id {}", outputDirectory, e);
return null;
}
}
private boolean isDirectoryEmpty(Path dir) throws IOException {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dir)) {
return !dirStream.iterator()
.hasNext();
} catch (IOException e) {
LOGGER.debug("Unable to open directory stream for {}", dir.toString(), e);
throw e;
}
}
}