// 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.core; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterators; import org.janusgraph.core.log.LogProcessorFramework; import org.janusgraph.core.log.TransactionRecovery; import org.janusgraph.diskstorage.Backend; import org.janusgraph.diskstorage.StandardStoreManager; import org.janusgraph.diskstorage.configuration.*; import org.janusgraph.diskstorage.configuration.backend.CommonsConfiguration; import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.*; import org.janusgraph.graphdb.database.StandardJanusGraph; import org.janusgraph.graphdb.log.StandardLogProcessorFramework; import org.janusgraph.graphdb.log.StandardTransactionLogProcessor; import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.time.Instant; import java.util.Iterator; import java.util.regex.Pattern; /** * JanusGraphFactory is used to open or instantiate a JanusGraph graph database. * * @author Matthias Bröcheler (http://www.matthiasb.com) * @see JanusGraph */ public class JanusGraphFactory { private static final Logger log = LoggerFactory.getLogger(JanusGraphFactory.class); /** * Opens a {@link JanusGraph} database. * <p/> * If the argument points to a configuration file, the configuration file is loaded to configure the JanusGraph graph * If the string argument is a configuration short-cut, then the short-cut is parsed and used to configure the returned JanusGraph graph. * <p /> * A configuration short-cut is of the form: * [STORAGE_BACKEND_NAME]:[DIRECTORY_OR_HOST] * * @param shortcutOrFile Configuration file name or configuration short-cut * @return JanusGraph graph database configured according to the provided configuration * @see <a href="http://docs.janusgraph.org/latest/configuration.html">"Configuration" manual chapter</a> * @see <a href="http://docs.janusgraph.org/latest/config-ref.html">Configuration Reference</a> */ public static JanusGraph open(String shortcutOrFile) { return open(getLocalConfiguration(shortcutOrFile)); } /** * Opens a {@link JanusGraph} database configured according to the provided configuration. * * @param configuration Configuration for the graph database * @return JanusGraph graph database * @see <a href="http://docs.janusgraph.org/latest/configuration.html">"Configuration" manual chapter</a> * @see <a href="http://docs.janusgraph.org/latest/config-ref.html">Configuration Reference</a> */ public static JanusGraph open(Configuration configuration) { return open(new CommonsConfiguration(configuration)); } /** * Opens a {@link JanusGraph} database configured according to the provided configuration. * * @param configuration Configuration for the graph database * @return JanusGraph graph database */ public static JanusGraph open(BasicConfiguration configuration) { return open(configuration.getConfiguration()); } /** * Opens a {@link JanusGraph} database configured according to the provided configuration. * * @param configuration Configuration for the graph database * @return JanusGraph graph database */ public static JanusGraph open(ReadConfiguration configuration) { return new StandardJanusGraph(new GraphDatabaseConfiguration(configuration)); } /** * Returns a {@link Builder} that allows to set the configuration options for opening a JanusGraph graph database. * <p /> * In the builder, the configuration options for the graph can be set individually. Once all options are configured, * the graph can be opened with {@link org.janusgraph.core.JanusGraphFactory.Builder#open()}. * * @return */ public static Builder build() { return new Builder(); } //--------------------- BUILDER ------------------------------------------- public static class Builder { private final WriteConfiguration writeConfiguration; private Builder() { writeConfiguration = new CommonsConfiguration(); } /** * Configures the provided configuration path to the given value. * * @param path * @param value * @return */ public Builder set(String path, Object value) { writeConfiguration.set(path, value); return this; } /** * Opens a JanusGraph graph with the previously configured options. * * @return */ public JanusGraph open() { ModifiableConfiguration mc = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS, writeConfiguration.copy(), BasicConfiguration.Restriction.NONE); return JanusGraphFactory.open(mc); } } /** * Returns a {@link org.janusgraph.core.log.LogProcessorFramework} for processing transaction log entries * against the provided graph instance. * * @param graph * @return */ public static LogProcessorFramework openTransactionLog(JanusGraph graph) { return new StandardLogProcessorFramework((StandardJanusGraph)graph); } /** * Returns a {@link TransactionRecovery} process for recovering partially failed transactions. The recovery process * will start processing the write-ahead transaction log at the specified transaction time. * * @param graph * @param start * @return */ public static TransactionRecovery startTransactionRecovery(JanusGraph graph, Instant start) { return new StandardTransactionLogProcessor((StandardJanusGraph)graph, start); } //################################### // HELPER METHODS //################################### private static ReadConfiguration getLocalConfiguration(String shortcutOrFile) { File file = new File(shortcutOrFile); if (file.exists()) return getLocalConfiguration(file); else { int pos = shortcutOrFile.indexOf(':'); if (pos<0) pos = shortcutOrFile.length(); String backend = shortcutOrFile.substring(0,pos); Preconditions.checkArgument(StandardStoreManager.getAllManagerClasses().containsKey(backend.toLowerCase()), "Backend shorthand unknown: %s", backend); String secondArg = null; if (pos+1<shortcutOrFile.length()) secondArg = shortcutOrFile.substring(pos + 1).trim(); BaseConfiguration config = new BaseConfiguration(); ModifiableConfiguration writeConfig = new ModifiableConfiguration(ROOT_NS,new CommonsConfiguration(config), BasicConfiguration.Restriction.NONE); writeConfig.set(STORAGE_BACKEND,backend); ConfigOption option = Backend.getOptionForShorthand(backend); if (option==null) { Preconditions.checkArgument(secondArg==null); } else if (option==STORAGE_DIRECTORY || option==STORAGE_CONF_FILE) { Preconditions.checkArgument(StringUtils.isNotBlank(secondArg),"Need to provide additional argument to initialize storage backend"); writeConfig.set(option,getAbsolutePath(secondArg)); } else if (option==STORAGE_HOSTS) { Preconditions.checkArgument(StringUtils.isNotBlank(secondArg),"Need to provide additional argument to initialize storage backend"); writeConfig.set(option,new String[]{secondArg}); } else throw new IllegalArgumentException("Invalid configuration option for backend "+option); return new CommonsConfiguration(config); } } /** * Load a properties file containing a JanusGraph graph configuration. * <p/> * <ol> * <li>Load the file contents into a {@link org.apache.commons.configuration.PropertiesConfiguration}</li> * <li>For each key that points to a configuration object that is either a directory * or local file, check * whether the associated value is a non-null, non-absolute path. If so, * then prepend the absolute path of the parent directory of the provided configuration {@code file}. * This has the effect of making non-absolute backend * paths relative to the config file's directory rather than the JVM's * working directory. * <li>Return the {@link ReadConfiguration} for the prepared configuration file</li> * </ol> * <p/> * * @param file A properties file to load * @return A configuration derived from {@code file} */ private static ReadConfiguration getLocalConfiguration(File file) { Preconditions.checkArgument(file != null && file.exists() && file.isFile() && file.canRead(), "Need to specify a readable configuration file, but was given: %s", file.toString()); try { PropertiesConfiguration configuration = new PropertiesConfiguration(file); final File tmpParent = file.getParentFile(); final File configParent; if (null == tmpParent) { /* * null usually means we were given a JanusGraph config file path * string like "foo.properties" that refers to the current * working directory of the process. */ configParent = new File(System.getProperty("user.dir")); } else { configParent = tmpParent; } Preconditions.checkNotNull(configParent); Preconditions.checkArgument(configParent.isDirectory()); // TODO this mangling logic is a relic from the hardcoded string days; it should be deleted and rewritten as a setting on ConfigOption final Pattern p = Pattern.compile("(" + Pattern.quote(STORAGE_NS.getName()) + "\\..*" + "(" + Pattern.quote(STORAGE_DIRECTORY.getName()) + "|" + Pattern.quote(STORAGE_CONF_FILE.getName()) + ")" + "|" + Pattern.quote(INDEX_NS.getName()) + "\\..*" + "(" + Pattern.quote(INDEX_DIRECTORY.getName()) + "|" + Pattern.quote(INDEX_CONF_FILE.getName()) + ")" + ")"); final Iterator<String> keysToMangle = Iterators.filter(configuration.getKeys(), new Predicate<String>() { @Override public boolean apply(String key) { if (null == key) return false; return p.matcher(key).matches(); } }); while (keysToMangle.hasNext()) { String k = keysToMangle.next(); Preconditions.checkNotNull(k); String s = configuration.getString(k); Preconditions.checkArgument(StringUtils.isNotBlank(s),"Invalid Configuration: key %s has null empty value",k); configuration.setProperty(k,getAbsolutePath(configParent,s)); } return new CommonsConfiguration(configuration); } catch (ConfigurationException e) { throw new IllegalArgumentException("Could not load configuration at: " + file, e); } } private static final String getAbsolutePath(String file) { return getAbsolutePath(new File(System.getProperty("user.dir")), file); } private static final String getAbsolutePath(final File configParent, String file) { File storedir = new File(file); if (!storedir.isAbsolute()) { String newFile = configParent.getAbsolutePath() + File.separator + file; log.debug("Overwrote relative path: was {}, now {}", file, newFile); return newFile; } else { log.debug("Loaded absolute path for key: {}", file); return file; } } }