/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.rest.maintenance;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.api.config.Settings;
import org.structr.common.error.FrameworkException;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.graph.MaintenanceCommand;
import org.structr.core.graph.NodeServiceCommand;
import org.structr.core.graph.Tx;
import org.structr.schema.export.StructrSchema;
import org.structr.schema.json.InvalidSchemaException;
import org.structr.schema.json.JsonSchema;
/**
*
*
*/
public class SnapshotCommand extends NodeServiceCommand implements MaintenanceCommand {
private static final Logger logger = LoggerFactory.getLogger(SnapshotCommand.class.getName());
@Override
public void execute(final Map<String, Object> attributes) throws FrameworkException {
final String mode = (String) attributes.get("mode");
final String name = (String) attributes.get("name");
final List<String> types = (List<String>) attributes.get("types");
execute(mode, name, types);
}
@Override
public boolean requiresEnclosingTransaction() {
return false;
}
@Override
public boolean requiresFlushingOfCaches() {
return false;
}
public void execute(final String mode, final String name) throws FrameworkException {
execute(mode, name, null);
}
public void execute(final String mode, final String name, final List<String> types) throws FrameworkException {
if (mode != null) {
switch (mode) {
case "export":
createSnapshot(name, types);
break;
case "restore":
restoreSnapshot(name);
break;
case "add":
addSnapshot(name);
break;
case "delete":
deleteSnapshot(name);
break;
case "purge":
purgeLocalSchema();
break;
default:
throw new FrameworkException(422, "Invalid mode supplied, valid values are export, restore, add, delete or delete.");
}
} else {
throw new FrameworkException(500, "No snapshot mode supplied, aborting.");
}
}
// ----- private methods -----
private void createSnapshot(final String name, final List<String> types) throws FrameworkException {
// we want to create a sorted, human-readble, diffable representation of the schema
final App app = StructrApp.getInstance();
// isolate write output
try (final Tx tx = app.tx()) {
final File snapshotFile = locateFile(name, true);
try (final Writer writer = new FileWriter(snapshotFile)) {
final JsonSchema schema = StructrSchema.createFromDatabase(app, types);
writer.append(schema.toString());
writer.append("\n"); // useful newline
writer.flush();
}
tx.success();
} catch (IOException | URISyntaxException ioex) {
logger.warn("", ioex);
}
}
private void restoreSnapshot(final String fileName) throws FrameworkException {
final App app = StructrApp.getInstance();
// isolate write output
try (final Tx tx = app.tx()) {
if (fileName != null) {
final File snapshotFile = locateFile(fileName, false);
try (final Reader reader = new FileReader(snapshotFile)) {
final JsonSchema schema = StructrSchema.createFromSource(reader);
StructrSchema.replaceDatabaseSchema(app, schema);
} catch (InvalidSchemaException iex) {
throw new FrameworkException(422, iex.getMessage());
}
} else {
throw new FrameworkException(422, "Please supply schema name to import.");
}
tx.success();
} catch (IOException | URISyntaxException ioex) {
logger.warn("", ioex);
}
}
private void purgeLocalSchema() throws FrameworkException {
final App app = StructrApp.getInstance();
// isolate write output
try (final Tx tx = app.tx()) {
try {
final JsonSchema schema = StructrSchema.createEmptySchema();
StructrSchema.replaceDatabaseSchema(app, schema);
} catch (InvalidSchemaException iex) {
throw new FrameworkException(422, iex.getMessage());
}
tx.success();
} catch (URISyntaxException use) {
logger.warn("", use);
}
}
private void addSnapshot(final String fileName) throws FrameworkException {
final App app = StructrApp.getInstance();
// isolate write output
try (final Tx tx = app.tx()) {
if (fileName != null) {
final File snapshotFile = locateFile(fileName, false);
try (final Reader reader = new FileReader(snapshotFile)) {
final JsonSchema schema = StructrSchema.createFromSource(reader);
StructrSchema.extendDatabaseSchema(app, schema);
} catch (InvalidSchemaException iex) {
throw new FrameworkException(422, iex.getMessage());
}
} else {
throw new FrameworkException(422, "Please supply schema name to import.");
}
tx.success();
} catch (IOException | URISyntaxException ioex) {
logger.warn("", ioex);
}
}
private void deleteSnapshot(final String fileName) throws FrameworkException {
if (fileName != null) {
final File snapshotFile = locateFile(fileName, false);
snapshotFile.delete();
} else {
throw new FrameworkException(422, "Please supply schema name to import.");
}
}
public static List<String> listSnapshots() {
final File baseDir = new File(getSnapshotsPath());
final List<String> fileNames = new LinkedList<>();
if (baseDir.exists()) {
final String[] names = baseDir.list();
if (names != null) {
fileNames.addAll(Arrays.asList(names));
}
}
Collections.sort(fileNames);
return fileNames;
}
public static File locateFile(final String name, final boolean addTimestamp) throws FrameworkException {
String fileName = name;
if (StringUtils.isBlank(fileName)) {
// create default value
fileName = "schema.json";
}
if (fileName.contains(System.getProperty("dir.separator", "/"))) {
throw new FrameworkException(422, "Only relative file names are allowed, please use the snapshot.path configuration setting to supply a custom path for snapshots.");
}
if (addTimestamp) {
final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-HHmmss");
fileName = format.format(System.currentTimeMillis()) + "-" + fileName;
}
// append JSON extension
if (!fileName.endsWith(".json")) {
fileName = fileName + ".json";
}
// create
final File path = new File(getSnapshotsPath() + fileName);
final File parent = path.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
return path;
}
public static String getSnapshotsPath() {
return Settings.getFullSettingPath(Settings.SnapshotsPath);
}
}