Create.java
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.console.command;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.rdf4j.common.io.IOUtil;
import org.eclipse.rdf4j.console.ConsoleIO;
import org.eclipse.rdf4j.console.ConsoleState;
import org.eclipse.rdf4j.console.Util;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.impl.LinkedHashModel;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.util.Models;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.repository.RepositoryReadOnlyException;
import org.eclipse.rdf4j.repository.config.ConfigTemplate;
import org.eclipse.rdf4j.repository.config.RepositoryConfig;
import org.eclipse.rdf4j.repository.config.RepositoryConfigException;
import org.eclipse.rdf4j.repository.config.RepositoryConfigSchema;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.helpers.StatementCollector;
import org.jline.reader.EndOfFileException;
import org.jline.reader.UserInterruptException;
/**
* Create command
*
* @author Dale Visser
*/
public class Create extends ConsoleCommand {
private static final String TEMPLATES_SUBDIR = "templates";
private static final String FILE_EXT = ".ttl";
private final File templatesDir;
@Override
public String getName() {
return "create";
}
@Override
public String getHelpShort() {
return "Creates a new repository";
}
@Override
public String getHelpLong() {
return PrintHelp.USAGE + "create <template> Create a new repository using this configuration template\n"
+ " built-in: \n" + Util.formatToWidth(80, " ", getBuiltinTemplates(), ", ") + "\n"
+ " template-dir (" + templatesDir + "):\n" + Util.formatToWidth(80, " ", getUserTemplates(), ", ");
}
/**
* Constructor
*
* @param consoleIO
* @param state
*/
public Create(ConsoleIO consoleIO, ConsoleState state) {
super(consoleIO, state);
this.templatesDir = new File(state.getDataDirectory(), TEMPLATES_SUBDIR);
}
@Override
public void execute(String... tokens) {
if (tokens.length < 2) {
writeln(getHelpLong());
} else {
createRepository(tokens[1]);
}
}
/**
* Return a concatenated list of ordered configuration templates files, without file type extension.
*
* @param path path with templates
* @return string concatenated string
* @throws IOException
*/
private String getOrderedTemplates(Path path) throws IOException {
try (Stream<Path> walk = Files.walk(path)) {
return walk
.filter(Files::isRegularFile)
.map(f -> f.getFileName().toString())
.filter(s -> s.endsWith(FILE_EXT))
.map(s -> s.substring(0, s.length() - FILE_EXT.length()))
.sorted()
.collect(Collectors.joining(", "));
}
}
/**
* Get the names of the user-defined repository templates, located in the templates directory.
*
* @return ordered array of names
*/
private String getUserTemplates() {
if (templatesDir == null || !templatesDir.exists() || !templatesDir.isDirectory()) {
return "";
}
try {
return getOrderedTemplates(templatesDir.toPath());
} catch (IOException ioe) {
writeError("Failed to read templates directory repository ", ioe);
}
return "";
}
/**
* Get the names of the built-in repository templates, located in the JAR containing RepositoryConfig.
*
* @return concatenated list of names
*/
private String getBuiltinTemplates() {
// assume the templates are all located in the same jar "directory" as the RepositoryConfig class
Class cl = RepositoryConfig.class;
try {
URI dir = cl.getResource(cl.getSimpleName() + ".class").toURI();
if (dir.getScheme().equals("jar")) {
try (FileSystem fs = FileSystems.newFileSystem(dir, Collections.EMPTY_MAP, null)) {
// turn package structure into directory structure
String pkg = cl.getPackage().getName().replaceAll("\\.", "/");
return getOrderedTemplates(fs.getPath(pkg));
}
}
} catch (NullPointerException | URISyntaxException | IOException e) {
writeError("Could not get built-in config templates from JAR", e);
}
return "";
}
/**
* Create a new repository based on a template
*
* @param templateName name of the template
*/
private void createRepository(final String templateName) {
try {
// FIXME: remove assumption of .ttl extension
final String templateFileName = templateName + FILE_EXT;
final File templateFile = new File(templatesDir, templateFileName);
InputStream templateStream = createTemplateStream(templateName, templateFileName, templatesDir,
templateFile);
if (templateStream != null) {
String template;
try {
template = IOUtil.readString(new InputStreamReader(templateStream, StandardCharsets.UTF_8));
} finally {
templateStream.close();
}
final ConfigTemplate configTemplate = new ConfigTemplate(template);
final Map<String, String> valueMap = new HashMap<>();
final Map<String, List<String>> variableMap = configTemplate.getVariableMap();
boolean eof = inputParameters(valueMap, variableMap, configTemplate.getMultilineMap());
if (!eof) {
final String configString = configTemplate.render(valueMap);
final Model graph = new LinkedHashModel();
final RDFParser rdfParser = Rio.createParser(RDFFormat.TURTLE, SimpleValueFactory.getInstance());
rdfParser.setRDFHandler(new StatementCollector(graph));
rdfParser.parse(new StringReader(configString), RepositoryConfigSchema.NAMESPACE);
final Resource repositoryNode = Models
.subject(graph.getStatements(null, RDF.TYPE, RepositoryConfigSchema.REPOSITORY))
.orElseThrow(() -> new RepositoryConfigException("missing repository node"));
final RepositoryConfig repConfig = RepositoryConfig.create(graph, repositoryNode);
repConfig.validate();
String overwrite = "WARNING: you are about to overwrite the configuration of an existing repository!";
boolean proceedOverwrite = this.state.getManager().hasRepositoryConfig(repConfig.getID())
? askProceed(overwrite, false)
: true;
String suggested = this.state.getManager().getNewRepositoryID(repConfig.getID());
String invalid = "WARNING: There are potentially incompatible characters in the repository id.";
boolean proceedInvalid = !suggested.startsWith(repConfig.getID())
? askProceed(invalid, false)
: true;
if (proceedInvalid && proceedOverwrite) {
try {
this.state.getManager().addRepositoryConfig(repConfig);
writeInfo("Repository created");
} catch (RepositoryReadOnlyException e) {
this.state.getManager().addRepositoryConfig(repConfig);
writeInfo("Repository created");
}
} else {
writeln("Create aborted");
}
}
}
} catch (EndOfFileException | UserInterruptException e) {
writeError("Create repository aborted", e);
throw e;
} catch (Exception e) {
writeError("Failed to create repository", e);
}
}
/**
* Ask user to specify values for the template variables
*
* @param valueMap
* @param variableMap
* @param multilineInput
* @return
*/
private boolean inputParameters(final Map<String, String> valueMap, final Map<String, List<String>> variableMap,
Map<String, String> multilineInput) {
if (!variableMap.isEmpty()) {
writeln("Please specify values for the following variables:");
}
boolean eof = false;
for (Map.Entry<String, List<String>> entry : variableMap.entrySet()) {
final String var = entry.getKey();
final List<String> values = entry.getValue();
StringBuilder sb = new StringBuilder();
sb.append(var);
if (values.size() > 1) {
sb.append(" (");
for (int i = 0; i < values.size(); i++) {
if (i > 0) {
sb.append("|");
}
sb.append(values.get(i));
}
sb.append(")");
}
if (!values.isEmpty()) {
sb.append(" [" + values.get(0) + "]");
}
String prompt = sb.append(": ").toString();
String value = multilineInput.containsKey(var) ? consoleIO.readMultiLineInput(prompt)
: consoleIO.readln(prompt);
eof = (value == null);
if (eof) {
break; // for loop
}
value = value.trim();
if (value.isEmpty()) {
value = null; // NOPMD
}
valueMap.put(var, value);
}
return eof;
}
/**
* Create input stream from a template file in the specified file directory. If the file cannot be found, try to
* read it from the embedded java resources instead.
*
* @param templateName name of the template
* @param templateFileName template file name
* @param templatesDir template directory
* @param templateFile template file
* @return input stream of the template
* @throws FileNotFoundException
*/
private InputStream createTemplateStream(final String templateName, final String templateFileName,
final File templatesDir, final File templateFile) throws FileNotFoundException {
InputStream templateStream = null;
if (templateFile.exists()) {
if (templateFile.canRead()) {
templateStream = new FileInputStream(templateFile);
} else {
writeError("Not allowed to read template file: " + templateFile);
}
} else {
// Try class path for built-ins
templateStream = RepositoryConfig.class.getResourceAsStream(templateFileName);
if (templateStream == null) {
writeError("No template called " + templateName + " found in " + templatesDir);
}
}
return templateStream;
}
}