// 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.backend; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.janusgraph.core.JanusGraphException; import org.janusgraph.diskstorage.BackendException; import org.janusgraph.diskstorage.configuration.Configuration; import org.janusgraph.diskstorage.util.time.TimestampProvider; import org.janusgraph.diskstorage.Entry; import org.janusgraph.diskstorage.StaticBuffer; import org.janusgraph.diskstorage.configuration.ConcurrentWriteConfiguration; import org.janusgraph.diskstorage.configuration.ReadConfiguration; import org.janusgraph.diskstorage.configuration.WriteConfiguration; import org.janusgraph.diskstorage.keycolumnvalue.*; import org.janusgraph.diskstorage.util.BackendOperation; import org.janusgraph.diskstorage.util.BufferUtil; import org.janusgraph.diskstorage.util.StaticArrayBuffer; import org.janusgraph.diskstorage.util.StaticArrayEntry; import org.janusgraph.graphdb.database.serialize.DataOutput; import org.janusgraph.graphdb.database.serialize.StandardSerializer; import org.janusgraph.util.system.IOUtils; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.commons.lang.StringUtils; import javax.annotation.Nullable; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.TIMESTAMP_PROVIDER; /** * @author Matthias Broecheler (me@matthiasb.com) */ public class KCVSConfiguration implements ConcurrentWriteConfiguration { private final BackendOperation.TransactionalProvider txProvider; private final TimestampProvider times; private final KeyColumnValueStore store; private final String identifier; private final StaticBuffer rowKey; private final StandardSerializer serializer; private Duration maxOperationWaitTime = Duration.ofMillis(10000L); public KCVSConfiguration(BackendOperation.TransactionalProvider txProvider, Configuration config, KeyColumnValueStore store, String identifier) throws BackendException { Preconditions.checkArgument(txProvider!=null && store!=null && config!=null); Preconditions.checkArgument(StringUtils.isNotBlank(identifier)); this.txProvider = txProvider; this.times = config.get(TIMESTAMP_PROVIDER); this.store = store; this.identifier = identifier; this.rowKey = string2StaticBuffer(this.identifier); this.serializer = new StandardSerializer(); } public void setMaxOperationWaitTime(Duration waitTime) { Preconditions.checkArgument(Duration.ZERO.compareTo(waitTime) < 0, "Wait time must be nonnegative: %s", waitTime); this.maxOperationWaitTime = waitTime; } /** * Reads the configuration property for this StoreManager * * @param key Key identifying the configuration property * @return Value stored for the key or null if the configuration property has not (yet) been defined. * @throws org.janusgraph.diskstorage.BackendException */ @Override public <O> O get(final String key, final Class<O> datatype) { StaticBuffer column = string2StaticBuffer(key); final KeySliceQuery query = new KeySliceQuery(rowKey,column, BufferUtil.nextBiggerBuffer(column)); StaticBuffer result = BackendOperation.execute(new BackendOperation.Transactional<StaticBuffer>() { @Override public StaticBuffer call(StoreTransaction txh) throws BackendException { List<Entry> entries = store.getSlice(query,txh); if (entries.isEmpty()) return null; return entries.get(0).getValueAs(StaticBuffer.STATIC_FACTORY); } @Override public String toString() { return "getConfiguration"; } }, txProvider, times, maxOperationWaitTime); if (result==null) return null; return staticBuffer2Object(result, datatype); } public<O> void set(String key, O value, O expectedValue) { set(key,value,expectedValue,true); } /** * Sets a configuration property for this StoreManager. * * @param key Key identifying the configuration property * @param value Value to be stored for the key * @throws org.janusgraph.diskstorage.BackendException */ @Override public <O> void set(String key, O value) { set(key,value,null,false); } public <O> void set(String key, O value, O expectedValue, final boolean checkExpectedValue) { final StaticBuffer column = string2StaticBuffer(key); final List<Entry> additions; final List<StaticBuffer> deletions; if (value!=null) { //Addition additions = new ArrayList<Entry>(1); deletions = KeyColumnValueStore.NO_DELETIONS; StaticBuffer val = object2StaticBuffer(value); additions.add(StaticArrayEntry.of(column, val)); } else { //Deletion additions = KeyColumnValueStore.NO_ADDITIONS; deletions = Lists.newArrayList(column); } final StaticBuffer expectedValueBuffer; if (checkExpectedValue && expectedValue!=null) { expectedValueBuffer = object2StaticBuffer(expectedValue); } else { expectedValueBuffer = null; } BackendOperation.execute(new BackendOperation.Transactional<Boolean>() { @Override public Boolean call(StoreTransaction txh) throws BackendException { if (checkExpectedValue) store.acquireLock(rowKey,column,expectedValueBuffer,txh); store.mutate(rowKey, additions, deletions, txh); return true; } @Override public String toString() { return "setConfiguration"; } }, txProvider, times, maxOperationWaitTime); } @Override public void remove(String key) { set(key,null); } @Override public WriteConfiguration copy() { throw new UnsupportedOperationException(); } private Map<String,Object> toMap() { Map<String,Object> entries = Maps.newHashMap(); List<Entry> result = BackendOperation.execute(new BackendOperation.Transactional<List<Entry>>() { @Override public List<Entry> call(StoreTransaction txh) throws BackendException { return store.getSlice(new KeySliceQuery(rowKey, BufferUtil.zeroBuffer(1), BufferUtil.oneBuffer(128)),txh); } @Override public String toString() { return "setConfiguration"; } },txProvider, times, maxOperationWaitTime); for (Entry entry : result) { String key = staticBuffer2String(entry.getColumnAs(StaticBuffer.STATIC_FACTORY)); Object value = staticBuffer2Object(entry.getValueAs(StaticBuffer.STATIC_FACTORY), Object.class); entries.put(key,value); } return entries; } public ReadConfiguration asReadConfiguration() { final Map<String,Object> entries = toMap(); return new ReadConfiguration() { @Override public <O> O get(String key, Class<O> datatype) { Preconditions.checkArgument(!entries.containsKey(key) || datatype.isAssignableFrom(entries.get(key).getClass())); return (O)entries.get(key); } @Override public Iterable<String> getKeys(final String prefix) { return Lists.newArrayList(Iterables.filter(entries.keySet(),new Predicate<String>() { @Override public boolean apply(@Nullable String s) { assert s!=null; return StringUtils.isBlank(prefix) || s.startsWith(prefix); } })); } @Override public void close() { //Do nothing } }; } @Override public Iterable<String> getKeys(String prefix) { return asReadConfiguration().getKeys(prefix); } @Override public void close() { try { store.close(); txProvider.close(); IOUtils.closeQuietly(serializer); } catch (BackendException e) { throw new JanusGraphException("Could not close configuration store",e); } } private StaticBuffer string2StaticBuffer(final String s) { ByteBuffer out = ByteBuffer.wrap(s.getBytes(Charset.forName("UTF-8"))); return StaticArrayBuffer.of(out); } private String staticBuffer2String(final StaticBuffer s) { return new String(s.as(StaticBuffer.ARRAY_FACTORY),Charset.forName("UTF-8")); } private<O> StaticBuffer object2StaticBuffer(final O value) { if (value==null) throw Graph.Variables.Exceptions.variableValueCanNotBeNull(); if (!serializer.validDataType(value.getClass())) throw Graph.Variables.Exceptions.dataTypeOfVariableValueNotSupported(value); DataOutput out = serializer.getDataOutput(128); out.writeClassAndObject(value); return out.getStaticBuffer(); } private<O> O staticBuffer2Object(final StaticBuffer s, Class<O> datatype) { Object value = serializer.readClassAndObject(s.asReadBuffer()); Preconditions.checkArgument(datatype.isInstance(value),"Could not deserialize to [%s], got: %s",datatype,value); return (O)value; } }