// 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.diskstorage.configuration; import com.google.common.base.Preconditions; import org.janusgraph.core.schema.JanusGraphConfiguration; import java.lang.reflect.Array; import java.time.Duration; /** * Helper class for inspecting and modifying a configuration for JanusGraph. * It is important to {@link #close()} the configuration when all changes have been made. * * @author Matthias Broecheler (me@matthiasb.com) */ public class UserModifiableConfiguration implements JanusGraphConfiguration { private final ModifiableConfiguration config; private final ConfigVerifier verifier; public UserModifiableConfiguration(ModifiableConfiguration config) { this(config,ALLOW_ALL); } public UserModifiableConfiguration(ModifiableConfiguration config, ConfigVerifier verifier) { Preconditions.checkArgument(config!=null && verifier!=null); this.config = config; this.verifier = verifier; } /** * Returns the backing configuration as a {@link ReadConfiguration} that can be used * to create and configure a JanusGraph graph. * * @return */ public ReadConfiguration getConfiguration() { return config.getConfiguration(); } @Override public String get(String path) { ConfigElement.PathIdentifier pp = ConfigElement.parse(config.getRootNamespace(),path); if (pp.element.isNamespace()) { ConfigNamespace ns = (ConfigNamespace)pp.element; StringBuilder s = new StringBuilder(); if (ns.isUmbrella() && !pp.lastIsUmbrella) { for (String sub : config.getContainedNamespaces(ns,pp.umbrellaElements)) { s.append("+ ").append(sub).append("\n"); } } /* else { for (ConfigElement element : ns.getChildren()) { s.append(ConfigElement.toStringSingle(element)).append("\n"); } } */ return s.toString(); } else { Object value; if (config.has((ConfigOption)pp.element,pp.umbrellaElements) || ((ConfigOption) pp.element).getDefaultValue()!=null) { value = config.get((ConfigOption)pp.element,pp.umbrellaElements); } else { return "null"; } Preconditions.checkNotNull(value); if (value.getClass().isArray()) { StringBuilder s = new StringBuilder(); s.append("["); for (int i=0;i<Array.getLength(value);i++) { if (i>0) s.append(","); s.append(Array.get(value,i)); } s.append("]"); return s.toString(); } else return String.valueOf(value); } } @Override public UserModifiableConfiguration set(String path, Object value) { ConfigElement.PathIdentifier pp = ConfigElement.parse(config.getRootNamespace(),path); Preconditions.checkArgument(pp.element.isOption(),"Need to provide configuration option - not namespace: %s",path); ConfigOption option = (ConfigOption)pp.element; verifier.verifyModification(option); if (option.getDatatype().isArray()) { Class arrayType = option.getDatatype().getComponentType(); Object arr; if (value.getClass().isArray()) { int size = Array.getLength(value); arr = Array.newInstance(arrayType,size); for (int i=0;i<size;i++) { Array.set(arr,i,convertBasic(Array.get(value,i),arrayType)); } } else { arr = Array.newInstance(arrayType,1); Array.set(arr,0,convertBasic(value,arrayType)); } value = arr; } else { value = convertBasic(value,option.getDatatype()); } config.set(option,value,pp.umbrellaElements); return this; } /** * Closes this configuration handler */ public void close() { config.close(); } private static final Object convertBasic(Object value, Class datatype) { if (Number.class.isAssignableFrom(datatype)) { Preconditions.checkArgument(value instanceof Number,"Expected a number but got: %s",value); Number n = (Number)value; if (datatype==Long.class) { return n.longValue(); } else if (datatype==Integer.class) { return n.intValue(); } else if (datatype==Short.class) { return n.shortValue(); } else if (datatype==Byte.class) { return n.byteValue(); } else if (datatype==Float.class) { return n.floatValue(); } else if (datatype==Double.class) { return n.doubleValue(); } else throw new IllegalArgumentException("Unexpected number data type: " + datatype); } else if (datatype==Boolean.class) { Preconditions.checkArgument(value instanceof Boolean,"Expected boolean value: %s",value); return value; } else if (datatype==String.class) { Preconditions.checkArgument(value instanceof String,"Expected string value: %s",value); return value; } else if (Duration.class.isAssignableFrom(datatype)) { Preconditions.checkArgument(value instanceof Duration,"Expected duration value: %s",value); return value; } else if (datatype.isEnum()) { // Check if value is an enum instance for (Object e : datatype.getEnumConstants()) if (e.equals(value)) return e; // Else toString() it and try to parse it as an enum value for (Object e : datatype.getEnumConstants()) if (e.toString().equals(value.toString())) return e; throw new IllegalArgumentException("No match for " + value + " in enum " + datatype); } else throw new IllegalArgumentException("Unexpected data type: " + datatype ); } public interface ConfigVerifier { /** * Throws an exception if the given configuration option is not allowed to be changed. * Otherwise just returns. * @param option */ public void verifyModification(ConfigOption option); } public static final ConfigVerifier ALLOW_ALL = new ConfigVerifier() { @Override public void verifyModification(ConfigOption option) { //Do nothing; } }; }