Java tutorial
/** * Copyright (C) 2015 meltmedia (christian.trimble@meltmedia.com) * * 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 com.meltmedia.dropwizard.etcd.json; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import mousio.etcd4j.EtcdClient; import mousio.etcd4j.responses.EtcdException; import mousio.etcd4j.responses.EtcdKeysResponse; /** * A dao for Etcd directories where all values in the directory are of a common type. * * @author Christian Trimble * * @param <T> the type of the values in this directory. */ public class EtcdDirectoryDao<T> { ObjectMapper mapper; Supplier<EtcdClient> clientSupplier; TypeReference<T> type; String directory; public EtcdDirectoryDao(Supplier<EtcdClient> clientSupplier, String directory, ObjectMapper mapper, TypeReference<T> type) { this.clientSupplier = clientSupplier; this.mapper = mapper; this.type = type; this.directory = directory; } /** * Puts a value into the directory and returns the etcd index of the value. The key should not start with the '/' character. * * @param key * @param entry * @return */ public Long put(String key, T entry) { try { return clientSupplier.get().put(directory + "/" + key, mapper.writeValueAsString(entry)).send() .get().node.modifiedIndex; } catch (Exception e) { throw new EtcdDirectoryException(String.format("failed to put key %s", key), e); } } /** * Puts a value into the directory, with a time to live in seconds and returns the etcd index of the value. The key should * not start with the '/' character. * * @param key * @param entry * @param ttl * @return */ public Long putWithTtl(String key, T entry, Integer ttl) { try { return clientSupplier.get().put(directory + "/" + key, mapper.writeValueAsString(entry)).ttl(ttl).send() .get().etcdIndex; } catch (Exception e) { throw new EtcdDirectoryException(String.format("failed to put key %s", key), e); } } /** * Updates the time to live of a given key and value and returns the new etcd index of that value. * * @param key * @param entry * @param ttl * @return */ public Long update(String key, T entry, Integer ttl) { try { String value = mapper.writeValueAsString(entry); return clientSupplier.get().put(directory + "/" + key, value).ttl(ttl).prevValue(value).send() .get().etcdIndex; } catch (Exception e) { throw new EtcdDirectoryException(String.format("failed to update ttl on key %s", key), e); } } /** * Removes the specified key from this directory. * * @param key * @return */ public Long remove(String key) { try { return clientSupplier.get().delete(directory + "/" + key).send().get().etcdIndex; } catch (EtcdException e) { if (e.errorCode == 100) { return e.index.longValue(); } throw new EtcdDirectoryException(String.format("failed to delete key %s", key), e); } catch (Exception e) { throw new EtcdDirectoryException(String.format("failed to delete key %s", key), e); } } /** * Deletes this directory and all of its children. A new, empty directory is then created with * the same name and the etcd index of that directory is returned. * * @return */ public Long resetDirectory() { try { clientSupplier.get().deleteDir(directory).recursive().send().get(); } catch (Exception e) { } try { return clientSupplier.get().putDir(directory).isDir().send().get().etcdIndex; } catch (Exception e) { throw new EtcdDirectoryException(String.format("failed to reset directory %s", directory), e); } } /** * Returns a stream of the values in this directory. * * @return a stream of the values in this directory. */ public Stream<T> stream() { try { EtcdKeysResponse response = clientSupplier.get().getDir(directory).send().get(); if (response.node == null || response.node.nodes == null) { return Stream.empty(); } return response.node.nodes.stream().map(n -> { try { return mapper.readValue(n.value, type); } catch (Exception e) { return null; } }); } catch (Exception e) { if (e instanceof EtcdException && ((EtcdException) e).errorCode == 100) return Stream.empty(); throw new EtcdDirectoryException(String.format("failed to list directory %s", directory), e); } } /** * Gets the value for the specified key. * * @param key * @return */ public T get(String key) { try { return mapper.readValue(clientSupplier.get().get(directory + "/" + key).send().get().node.value, type); } catch (EtcdException e) { if (e.errorCode == 100) { throw new KeyNotFound(e.etcdMessage, e); } throw new EtcdDirectoryException(e.etcdMessage, e); } catch (Exception e) { throw new EtcdDirectoryException( String.format("could not load key %s from directory %s", key, directory), e); } } /** * Puts a new directory to the specified key. * * @param key * @return */ public Long putDir(String key) { try { return clientSupplier.get().putDir(directory + key).send().get().node.modifiedIndex; } catch (Exception e) { throw new EtcdDirectoryException(String.format("failed to put directory key %s", key), e); } } /** * Updates the value of key, using the specified transform, if its current value matches predicate. * * @param key * @param precondition * @param transform * @return */ public Long update(String key, Predicate<T> precondition, Function<T, T> transform) { T currentValue = get(key); if (!precondition.test(currentValue)) { throw new EtcdDirectoryException(String.format("precondition failed while updating %s", key)); } T nextValue = transform.apply(clone(currentValue)); return put(key, nextValue, currentValue); } /** * Helper method to clone objects of this directory's type. * * @param value * @return */ public T clone(T value) { return mapper.convertValue(mapper.convertValue(value, JsonNode.class), type); } /** * Puts the given value into key, if the previous value matches. */ public Long put(String key, T value, T previousValue) { try { return clientSupplier.get().put(directory + "/" + key, mapper.writeValueAsString(value)) .prevValue(mapper.writeValueAsString(previousValue)).send().get().node.modifiedIndex; } catch (Exception e) { throw new EtcdDirectoryException(String.format("failed to put key %s with previous value", key), e); } } }