All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.spi.cluster.consul.impl.ConversationUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2018-2019 Roman Levytskyi
 *
 * 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 io.vertx.spi.cluster.consul.impl;

import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.impl.ClusterSerializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.Base64;
import java.util.Optional;

/**
 * Dedicated utility to marshal objects into strings and un-marshal strings into objects.
 * Consul client doesn't support writing byte arrays to consul KV store.
 * In order to address the ability to save different types of java objects we:
 * - serialize objects and get their byte arrays.
 * - base64 encode this byte arrays - as a result we receive {@link String}.
 * - use this base 64 encoded string as "value" to put into consul kv store.
 * 

* Note: consider KRYO: [https://github.com/EsotericSoftware/kryo] to replace default java serialization here. * * @author Roman Levytskyi * @See {@link ConsulEntry} */ class ConversationUtils { private static final String KEY_TAG = "KEY"; private static final String VALUE_TAG = "VALUE"; private static final String NODE_ID_TAG = "NODE_ID"; private static final String TTL_TAG = "TTL"; private static final String INFO_TAG = "INFO"; /** * Serializes key and value and also appends the metadata. * * @param key actual key of an entry. * @param value action value of an entry. * @param nodeId id of the node that performs this operation. * @param ttl optional ttl that might be placed on entry. * @param key type. * @param value type. * @return json object that gets shipped as value to consul kv store. * @throws IOException in case an entry can be properly serialized. */ static String asString(K key, V value, String nodeId, Optional ttl) throws IOException { JsonObject jsonObject = new JsonObject() .put(KEY_TAG, asString(key)) .put(VALUE_TAG, asString(value)) .put(NODE_ID_TAG, nodeId) .put(INFO_TAG, value.toString()); ttl.ifPresent(ttlValue -> jsonObject.put(TTL_TAG, ttlValue)); return jsonObject.encodePrettily(); } /** * De-serializes consul map'value to an internal {@link ConsulEntry} * * @param consulValue base 64 encoded serialized consul value. * @param key type. * @param value type. * @return an internal {@link ConsulEntry} * @throws Exception in case consul value can be properly de-serialized. */ static ConsulEntry asConsulEntry(String consulValue) throws Exception { JsonObject jsonObject = new JsonObject(consulValue); K key = asObject(jsonObject.getString(KEY_TAG)); V value = asObject(jsonObject.getString(VALUE_TAG)); String nodeId = jsonObject.getString(NODE_ID_TAG); Optional ttl = Optional.ofNullable(jsonObject.getLong(TTL_TAG)); return new ConsulEntry<>(key, value, nodeId, ttl); } /** * Gets ttl out of consul value which is a {@link JsonObject}. * * @param consulEntry consul value. * @return ttl. */ static Optional asTtlConsulEntry(String consulEntry) { JsonObject jsonObject = new JsonObject(consulEntry); Optional ttl = Optional.ofNullable(jsonObject.getLong(TTL_TAG)); return ttl; } /** * {@link Future} wrapper around asString. */ static Future asFutureString(K key, V value, String nodeId) { Promise result = Promise.promise(); try { result.complete(asString(key, value, nodeId, Optional.empty())); } catch (IOException e) { result.fail(e); } return result.future(); } /** * {@link Future} wrapper around asString that takes into account TTL. */ static Future asFutureString(K key, V value, String nodeId, Long ttl) { Promise result = Promise.promise(); try { result.complete(asString(key, value, nodeId, Optional.ofNullable(ttl))); } catch (IOException e) { result.fail(e); } return result.future(); } static Future> asFutureConsulEntry(String object) { Promise> result = Promise.promise(); if (object == null) result.complete(); else { try { result.complete(asConsulEntry(object)); } catch (Exception e) { result.fail(e); } } return result.future(); } /** * Serializes any type of {@link Object} into base 64 encoded {@link String} by taking into account {@link ClusterSerializable}. * * @param object to be serialized. * @return serialized, base 64 encoded object. * @throws IOException in case object can't get serialized. */ private static String asString(Object object) throws IOException { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); DataOutput dataOutput = new DataOutputStream(byteOut); if (object instanceof ClusterSerializable) { ClusterSerializable clusterSerializable = (ClusterSerializable) object; dataOutput.writeBoolean(true); dataOutput.writeUTF(object.getClass().getName()); Buffer buffer = Buffer.buffer(); clusterSerializable.writeToBuffer(buffer); byte[] bytes = buffer.getBytes(); dataOutput.writeInt(bytes.length); dataOutput.write(bytes); } else { dataOutput.writeBoolean(false); ByteArrayOutputStream javaByteOut = new ByteArrayOutputStream(); ObjectOutput objectOutput = new ObjectOutputStream(javaByteOut); objectOutput.writeObject(object); dataOutput.write(javaByteOut.toByteArray()); } return Base64.getEncoder().encodeToString(byteOut.toByteArray()); } /** * De-serializes a plain {@link String} into an actual object by taking into account {@link ClusterSerializable}. * * @param object base 63 encoded string object for de - serialization. * @param type of de-serialized object. * @return de-serialized object. * @throws Exception in case object can't get de-serialized. */ private static T asObject(String object) throws Exception { final byte[] data = Base64.getDecoder().decode(object); ByteArrayInputStream byteIn = new ByteArrayInputStream(data); DataInputStream in = new DataInputStream(byteIn); boolean isClusterSerializable = in.readBoolean(); if (isClusterSerializable) { String className = in.readUTF(); Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); int length = in.readInt(); byte[] body = new byte[length]; in.readFully(body); try { ClusterSerializable clusterSerializable; //check clazz if have a public Constructor method. if (clazz.getConstructors().length == 0) { Constructor constructor = (Constructor) clazz.getDeclaredConstructor(); constructor.setAccessible(true); clusterSerializable = (ClusterSerializable) constructor.newInstance(); } else { clusterSerializable = (ClusterSerializable) clazz.newInstance(); } clusterSerializable.readFromBuffer(0, Buffer.buffer(body)); return (T) clusterSerializable; } catch (Exception e) { throw new IllegalStateException("Failed to load class " + e.getMessage(), e); } } else { byte[] body = new byte[in.available()]; in.readFully(body); ObjectInputStream objectIn = new ObjectInputStream(new ByteArrayInputStream(body)); return (T) objectIn.readObject(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy