// Copyright 2017 JanusGraph Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.janusgraph.util.system;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import org.janusgraph.diskstorage.configuration.ConfigElement;
import org.janusgraph.diskstorage.configuration.ConfigOption;
import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
import org.apache.commons.lang.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.LinkedList;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ConfigurationFileFilter {
private static final Pattern REPLACEMENT_PATTERN = Pattern.compile("^#JANUSGRAPHCFG\\{((.+)=(.*))\\}$");
private static final Logger log =
LoggerFactory.getLogger(ConfigurationFileFilter.class);
private static final int WRAP_COLUMNS = 72;
public static void main(String args[]) throws IOException {
int errors = filter(args[0], args[1]);
System.exit(Math.min(errors, 127));
}
public static int filter(String inputContextDirPath, String outputContextDirPath) throws IOException {
// Read args[0] as a dirname and iterate recursively over its file contents
File inputContextDir = new File(inputContextDirPath);
File outputContextDir = new File(outputContextDirPath);
log.info("Input context dir: {}", inputContextDir);
log.info("Output context dir: {}", outputContextDir);
Preconditions.checkArgument(inputContextDir.isDirectory(),
"Input context dir %s is not a directory", inputContextDir);
Preconditions.checkArgument(inputContextDir.canRead(),
"Input context dir %s is not readable", inputContextDir);
if (!outputContextDir.exists()) {
outputContextDir.mkdirs(); // may fail if path exists as a file
}
Queue<InputRecord> dirQueue = new LinkedList<InputRecord>();
dirQueue.add(new InputRecord(inputContextDir, File.separator));
int parseErrors = 0;
int visitedDirs = 0;
int processedFiles = 0;
InputRecord rec;
while (null != (rec = dirQueue.poll())) {
File curDir = rec.getDirectory();
String contextPath = rec.getContextPath();
Preconditions.checkState(curDir.exists());
Preconditions.checkState(curDir.isDirectory());
Preconditions.checkState(curDir.canRead());
visitedDirs++;
for (File f : curDir.listFiles()) {
if (f.isDirectory()) {
if (!f.canRead()) {
log.warn("Skipping unreadable directory {} in input basedir", f);
continue;
}
dirQueue.add(new InputRecord(f, contextPath + f.getName() + File.separator));
} else {
if (!f.canRead()) {
log.warn("Skipping unreadable file {} in input basedir", f);
continue;
}
File outputDir = new File(outputContextDir.getPath() + contextPath);
if (!outputDir.exists()) {
outputDir.mkdirs();
}
parseErrors += processFile(f, new File(outputContextDir.getPath() + contextPath + f.getName()));
processedFiles++;
}
}
}
String summaryTemplate = "Summary: visited {} dir(s) and processed {} file(s) with {} parse error(s).";
if (0 == parseErrors) {
log.info(summaryTemplate, visitedDirs, processedFiles, parseErrors);
} else {
log.error(summaryTemplate, visitedDirs, processedFiles, parseErrors);
}
return parseErrors;
}
private static class InputRecord {
private final File directory;
private final String contextPath;
private InputRecord(File directory, String relativePath) {
this.directory = directory;
this.contextPath = relativePath;
}
public File getDirectory() {
return directory;
}
public String getContextPath() {
return contextPath;
}
}
private static int processFile(File inputFile, File outputFile) throws IOException {
BufferedReader in = null;
PrintStream out = null;
int inputLines = 0;
int replacements = 0;
int parseErrors = 0;
try {
in = new BufferedReader(new FileReader(inputFile));
out = new PrintStream(outputFile);
String line;
while (null != (line = in.readLine())) {
inputLines++;
Matcher m = REPLACEMENT_PATTERN.matcher(line);
if (m.matches()) {
String cfgKey = m.group(2).trim();
m.group(3);
try {
ConfigElement.PathIdentifier pid = ConfigElement.parse(GraphDatabaseConfiguration.ROOT_NS, cfgKey);
ConfigOption<?> opt = (ConfigOption<?>) pid.element;
//opt.verify(cfgVal);
String kvPair = m.group(1);
String descr = "# " + WordUtils.wrap(opt.getDescription(), WRAP_COLUMNS, "\n# ", false);
String dt = "# Data Type: ";
if (opt.getDatatype().isArray()) {
dt += opt.getDatatype().getComponentType().toString() + "[]";
} else if (opt.getDatatype().isEnum()) {
Enum[] enums = (Enum[])opt.getDatatype().getEnumConstants();
String[] names = new String[enums.length];
for (int i = 0; i < names.length; i++)
names[i] = enums[i].name();
dt += opt.getDatatype().getSimpleName() + " enum:";
String s = "\n# " + "{ " + Joiner.on(", ").join(names) + " }";
dt += WordUtils.wrap(s, WRAP_COLUMNS, "\n# ", false);
} else {
dt += opt.getDatatype().getSimpleName();
}
String defval = "# Default: ";
if (null == opt.getDefaultValue()) {
defval += "(no default value)";
} else if (opt.getDatatype().isArray()) {
defval += Joiner.on(", ").join((Object[]) opt.getDefaultValue());
} else if (opt.getDatatype().isEnum()) {
defval += ((Enum)opt.getDefaultValue()).name();
} else {
defval += opt.getDefaultValue();
}
String mut = "# Mutability: " + opt.getType();
if (opt.isManaged()) {
mut += "\n#\n# ";
if (opt.getType().equals(ConfigOption.Type.FIXED)) {
mut += "This setting is " + opt.getType() +
" and cannot be changed after bootstrapping JanusGraph.";
} else {
final String warning =
"Settings with mutability " + opt.getType() + " are centrally managed in " +
"JanusGraph's storage backend. After starting the database for the first time, " +
"this file's copy of this setting is ignored. Use JanusGraph's Management " +
"System to read or modify this value after bootstrapping.";
mut += WordUtils.wrap(warning, WRAP_COLUMNS, "\n# ", false);
}
}
out.println(descr);
out.println("#");
out.println(defval);
out.println(dt);
out.println(mut);
out.println(kvPair);
replacements++;
} catch (RuntimeException e) {
out.println(line);
log.warn("Exception on {}:{}", inputFile, line, e);
parseErrors++;
}
} else {
out.println(line);
}
}
log.info("Read {}: {} lines, {} macro substitutions", inputFile, inputLines, replacements);
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
}
// Read what we just wrote. Make sure it validates as a JanusGraph config.
ConfigurationLint.Status stat = ConfigurationLint.validate(outputFile.getAbsolutePath());
if (0 != stat.getErrorSettingCount())
log.error("Output file {} failed to validate", outputFile);
parseErrors += stat.getErrorSettingCount();
return parseErrors;
}
}