/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.configuration.internal; import java.io.File; import java.io.IOException; import java.nio.file.Files; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.util.DefaultPrettyPrinter; import org.codehaus.jackson.util.DefaultPrettyPrinter.Lf2SpacesIndenter; import de.rcenvironment.core.configuration.ConfigurationException; import de.rcenvironment.core.configuration.ConfigurationSegment; import de.rcenvironment.core.utils.common.JsonUtils; /** * Default {@link ConfigurationStore} implementation. * * @author Robert Mischke * @author Doreen Seider */ public class ConfigurationStoreImpl implements ConfigurationStore { private File storageFile; private boolean backupFileCreated = false; private final Log log = LogFactory.getLog(getClass()); public ConfigurationStoreImpl(File storageFile) { this.storageFile = storageFile; } @Override public ConfigurationSegment getSnapshotOfRootSegment() throws IOException { try { if (!storageFile.exists()) { // return empty placeholder return new WritableConfigurationSegmentImpl(null); } ObjectMapper mapper = JsonUtils.getDefaultObjectMapper(); mapper.configure(org.codehaus.jackson.JsonParser.Feature.ALLOW_COMMENTS, true); mapper.configure(org.codehaus.jackson.JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); validateJsonSyntax(mapper); JsonNode node = mapper.readTree(storageFile); return new WritableConfigurationSegmentImpl(node); } catch (JsonParseException e) { throw new IOException("Malformed configuration file: " + e.toString()); } } // mapper.readTree() (the way to read the file in the first place) does not check the syntax of the entire file, but that's needed to // inform the user about a malformed configuration file. Feel free to improve the syntax check. Didn't find any nicer. private void validateJsonSyntax(ObjectMapper mapper) throws JsonParseException, IOException { try (JsonParser parser = mapper.getJsonFactory().createJsonParser(storageFile)) { // syntax check of entire file while (parser.nextToken() != null) { // nothing to do here parser.version(); // for checkstyle purposes } } } @Override public ConfigurationSegment createEmptyPlaceholder() { return new WritableConfigurationSegmentImpl(null); } /** * Custom Jackson JSON indenter that uses 4 spaces instead of 2. * * @author Robert Mischke */ private static class CustomIndenter extends Lf2SpacesIndenter { @Override public void writeIndentation(JsonGenerator jg, int level) throws IOException, JsonGenerationException { super.writeIndentation(jg, level * 2); } } @Override public void update(ConfigurationSegment segment) throws ConfigurationException, IOException { final WritableConfigurationSegmentImpl rootSegment = ((WritableConfigurationSegmentImpl) segment).getRootSegment(); if (rootSegment != segment) { // could be made to work nonetheless, but enforce good calling practice throw new IOException("The parameter passed to the save() method was not a root segment"); } // TODO >7.0.0: add proper locking! not thread-safe at the moment! // TODO >6.0.0: quick & dirty; make more reliable // only create single backup file per instance lifetime if (!backupFileCreated) { File backupFile = new File(storageFile.getParentFile(), storageFile.getName() + "." + System.currentTimeMillis() + ".bak"); log.debug("Creating backup of existing configuration file at " + backupFile); Files.move(storageFile.toPath(), backupFile.toPath()); backupFileCreated = true; } writeJsonFile(rootSegment.getSegmentRootNode(), storageFile); // NOTE: for debugging only // log.debug(FileUtils.readFileToString(storageFile)); } @Override public void exportToFile(ConfigurationSegment configurationSegment, File destinationFile) throws IOException { JsonNode segmentRootNode = ((WritableConfigurationSegmentImpl) configurationSegment).getSegmentRootNode(); if (segmentRootNode != null) { writeJsonFile(segmentRootNode, destinationFile); } else { FileUtils.write(destinationFile, "{}"); } } private void writeJsonFile(JsonNode jsonRootNode, File file) throws IOException { ObjectMapper mapper = JsonUtils.getDefaultObjectMapper(); JsonFactory jsonFactory = new JsonFactory(); try (JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(file, JsonEncoding.UTF8)) { DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); CustomIndenter customIndenter = new CustomIndenter(); prettyPrinter.indentObjectsWith(customIndenter); prettyPrinter.indentArraysWith(customIndenter); jsonGenerator.setPrettyPrinter(prettyPrinter); mapper.writeTree(jsonGenerator, jsonRootNode); } } }