com.vmware.dcp.common.DigestThreadLocal.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.dcp.common.DigestThreadLocal.java

Source

/*
 * Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 *
 * 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.vmware.dcp.common;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

import com.vmware.dcp.common.Service.Action;
import com.vmware.dcp.common.Service.ServiceOption;
import com.vmware.dcp.common.ServiceDocumentDescription.PropertyDescription;
import com.vmware.dcp.common.ServiceDocumentDescription.PropertyIndexingOption;
import com.vmware.dcp.common.ServiceDocumentDescription.PropertyUsageOption;
import com.vmware.dcp.common.ServiceDocumentDescription.TypeName;
import com.vmware.dcp.common.SystemHostInfo.OsFamily;
import com.vmware.dcp.common.serialization.BufferThreadLocal;
import com.vmware.dcp.common.serialization.JsonMapper;
import com.vmware.dcp.common.serialization.KryoSerializers.KryoForDocumentThreadLocal;
import com.vmware.dcp.common.serialization.KryoSerializers.KryoForObjectThreadLocal;
import com.vmware.dcp.services.common.ServiceUriPaths;

class DigestThreadLocal extends ThreadLocal<MessageDigest> {
    @Override
    protected MessageDigest initialValue() {
        return Utils.createDigest();
    }
}

/**
 * Runtime utility functions
 */
public class Utils {
    private static final int BUFFER_INITIAL_CAPACITY = 1 * 1024;
    private static final String CHARSET_UTF_8 = "UTF-8";
    public static final String CHARSET = CHARSET_UTF_8;
    public static final String UI_DIRECTORY_NAME = "ui";

    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
    private static final String HASH_NAME_SHA_1 = "SHA-1";
    public static final String DEFAULT_CONTENT_HASH = HASH_NAME_SHA_1;

    public static final int DEFAULT_THREAD_COUNT = Math.max(4, Runtime.getRuntime().availableProcessors());

    /**
     * {@link #isReachableByPing} launches a separate ping process to ascertain whether a given IP
     * address is reachable within a specified timeout. This constant extends the timeout for that
     * check to account for the start-up overhead of that process.
     */
    private static final long PING_LAUNCH_TOLERANCE_MS = 50;

    private static final KryoForObjectThreadLocal kryoForObjectPerThread = new KryoForObjectThreadLocal();
    private static final KryoForDocumentThreadLocal kryoForDocumentPerThread = new KryoForDocumentThreadLocal();
    private static final DigestThreadLocal digestPerThread = new DigestThreadLocal();
    private static final BufferThreadLocal bufferPerThread = new BufferThreadLocal();

    private static final JsonMapper JSON = new JsonMapper();
    private static final ConcurrentMap<Class<?>, JsonMapper> CUSTOM_JSON = new ConcurrentHashMap<>();

    private static JsonMapper getJsonMapperFor(Type type) {
        if (type instanceof Class) {
            return getJsonMapperFor((Class<?>) type);
        } else if (type instanceof ParameterizedType) {
            Type rawType = ((ParameterizedType) type).getRawType();
            return getJsonMapperFor(rawType);
        } else {
            return JSON;
        }
    }

    private static JsonMapper getJsonMapperFor(Object instance) {
        if (instance == null) {
            return JSON;
        }
        return getJsonMapperFor(instance.getClass());
    }

    private static JsonMapper getJsonMapperFor(Class<?> type) {
        if (type.isArray() && type != byte[].class) {
            type = type.getComponentType();
        }
        return CUSTOM_JSON.getOrDefault(type, JSON);
    }

    /**
     * Registers a specialized {@link JsonMapper} that should be used when serializing instances of
     * the specified class. This is useful when the class in question contains members that might
     * require special handling e.g. custom type adapters.
     *
     * @param clazz
     *            Identifies the class to which the custom serialization should occur. Will not be
     *            applicable for sub-classes or instances of this class embedded as a members in
     *            other (non-registered) types.
     *
     * @param mapper
     *            A {@link JsonMapper} for serializing/de-serializing service documents to/from
     *            JSON.
     */
    public static void registerCustomJsonMapper(Class<?> clazz, JsonMapper mapper) {
        CUSTOM_JSON.putIfAbsent(clazz, mapper);
    }

    public static <T> T clone(T t) {
        Kryo k = kryoForDocumentPerThread.get();
        T clone = k.copy(t);
        return clone;
    }

    public static <T> T cloneObject(T t) {
        Kryo k = kryoForObjectPerThread.get();
        T clone = k.copy(t);
        k.reset();
        return clone;
    }

    public static String computeSignature(ServiceDocument s, ServiceDocumentDescription description) {
        if (description == null) {
            throw new IllegalArgumentException("description is required");
        }

        byte[] buffer = getBuffer(description.serializedStateSizeLimit);
        int position = 0;

        for (PropertyDescription pd : description.propertyDescriptions.values()) {
            if (pd.indexingOptions != null) {
                if (pd.indexingOptions.contains(PropertyIndexingOption.EXCLUDE_FROM_SIGNATURE)) {
                    continue;
                }
            }

            Object fieldValue = ReflectionUtils.getPropertyValue(pd, s);
            if (pd.typeName == TypeName.COLLECTION || pd.typeName == TypeName.MAP || pd.typeName == TypeName.PODO
                    || pd.typeName == TypeName.ARRAY || pd.typeName == TypeName.BYTES) {
                position = Utils.toBytes(fieldValue, buffer, position);
            } else if (fieldValue != null) {
                position = Utils.toBytes(fieldValue, buffer, position);
            }
        }

        return computeHash(buffer, 0, position);
    }

    public static byte[] getBuffer(int capacity) {

        byte[] buffer = bufferPerThread.get();
        if (buffer.length < capacity) {
            buffer = new byte[capacity];
            bufferPerThread.set(buffer);
        }

        if (buffer.length > capacity * 10) {
            buffer = new byte[capacity];
            bufferPerThread.set(buffer);
        }
        return buffer;
    }

    public static int toBytes(Object o, byte[] buffer, int position) {
        Kryo k = kryoForObjectPerThread.get();
        Output out = new Output(buffer);
        out.setPosition(position);
        k.writeClassAndObject(out, o);
        return out.position();
    }

    public static int toBytes(ServiceDocument o, byte[] buffer, int position) {
        Kryo k = kryoForDocumentPerThread.get();
        Output out = new Output(buffer);
        out.setPosition(position);
        k.writeClassAndObject(out, o);
        return out.position();
    }

    public static Object fromBytes(byte[] bytes) {
        return fromBytes(bytes, 0, bytes.length);
    }

    public static Object fromBytes(byte[] bytes, int offset, int length) {
        Kryo k = kryoForObjectPerThread.get();
        Input in = new Input(bytes, offset, length);
        return k.readClassAndObject(in);
    }

    public static Object fromDocumentBytes(byte[] bytes, int offset, int length) {
        Kryo k = kryoForDocumentPerThread.get();
        Input in = new Input(bytes, offset, length);
        return k.readClassAndObject(in);
    }

    public static void performMaintenance() {

    }

    public static String computeHash(String content) {
        byte[] source = content.getBytes(Charset.forName(CHARSET_UTF_8));
        return computeHash(source, 0, source.length);
    }

    private static String computeHash(byte[] content, int offset, int length) {
        MessageDigest digest = digestPerThread.get();
        digest.update(content, offset, length);
        byte[] hash = digest.digest();
        return Utils.toHexString(hash);
    }

    private static void appendJson(Object obj, Appendable buf) {
        JsonMapper mapper = getJsonMapperFor(obj);
        mapper.toJson(obj, buf);
    }

    public static String toJson(Object body) {
        if (body instanceof String) {
            return (String) body;
        }
        StringBuilder content = new StringBuilder(BUFFER_INITIAL_CAPACITY);
        appendJson(body, content);
        return content.toString();
    }

    public static String toJsonHtml(Object body) {
        return getJsonMapperFor(body).toJsonHtml(body);
    }

    public static <T> T fromJson(String json, Class<T> clazz) {
        return getJsonMapperFor(clazz).fromJson(json, clazz);
    }

    public static <T> T fromJson(Object json, Class<T> clazz) {
        return getJsonMapperFor(clazz).fromJson(json, clazz);
    }

    public static <T> T fromJson(Object json, Type type) {
        return getJsonMapperFor(type).fromJson(json, type);
    }

    public static <T> T getJsonMapValue(Object json, String key, Class<T> valueClazz) {
        Map<String, JsonElement> runtimeMap = Utils.fromJson(json, new TypeToken<Map<String, JsonElement>>() {
        }.getType());
        return Utils.fromJson(runtimeMap.get(key), valueClazz);
    }

    public static <T> T getJsonMapValue(Object json, String key, Type valueType) {
        Map<String, JsonElement> runtimeMap = Utils.fromJson(json, new TypeToken<Map<String, JsonElement>>() {
        }.getType());
        return Utils.fromJson(runtimeMap.get(key), valueType);
    }

    public static String toString(Throwable t) {
        StringWriter writer = new StringWriter();
        try (PrintWriter printer = new PrintWriter(writer);) {
            t.printStackTrace(printer);
        }

        return writer.toString();
    }

    public static String toString(Map<?, Throwable> exceptions) {
        StringWriter writer = new StringWriter();
        try (PrintWriter printer = new PrintWriter(writer);) {
            for (Throwable t : exceptions.values()) {
                t.printStackTrace(printer);
            }
        }

        return writer.toString();
    }

    public static String getCurrentFileDirectory() {
        try {
            return new File(".").getCanonicalPath();
        } catch (IOException e) {
            Logger.getAnonymousLogger().warning(Utils.toString(e));
            return null;
        }
    }

    public static void log(Class<?> type, String classOrUri, Level level, String fmt, Object... args) {
        Logger lg = Logger.getLogger(type.getName());
        log(lg, 3, classOrUri, level, fmt, args);
    }

    public static void log(Logger lg, Integer nestingLevel, String classOrUri, Level level, String fmt,
            Object... args) {
        if (nestingLevel == null) {
            nestingLevel = 2;
        }
        Level l = lg.getLevel();
        if (l == null) {
            l = lg.getParent().getLevel();
        }
        if (l.intValue() > level.intValue()) {
            return;
        }

        LogRecord lr = new LogRecord(level, String.format(fmt, args));
        Exception e = new Exception();
        StackTraceElement[] stack = e.getStackTrace();
        if (stack.length > nestingLevel) {
            lr.setSourceMethodName(stack[nestingLevel].getMethodName());
        }
        lr.setSourceClassName(classOrUri);
        lg.log(lr);
    }

    public static void logWarning(String fmt, Object... args) {
        Logger.getAnonymousLogger().warning(String.format(fmt, args));
    }

    private static AtomicLong prevTime = new AtomicLong();
    private static long timeComparisonEpsilonMicros = TimeUnit.SECONDS.toMicros(120);

    /**
     * Return wall clock time, in microseconds since Unix Epoch (1/1/1970 UTC midnight). This
     * functions guarantees time always moves forward, but it does not guarantee it does so in fixed
     * intervals.
     *
     * @return
     */
    public static long getNowMicrosUtc() {
        long now = System.currentTimeMillis() * 1000;
        long time = prevTime.getAndIncrement();

        // Only set time if current time is greater than our stored time.
        if (now > time) {
            // This CAS can fail; getAndIncrement() ensures no value is returned twice.
            prevTime.compareAndSet(time + 1, now);
            return prevTime.getAndIncrement();
        }

        return time;
    }

    public static String buildKind(Class<?> type) {
        return type.getCanonicalName().replace(".", ":");
    }

    public static ServiceErrorResponse toServiceErrorResponse(Throwable e) {
        return ServiceErrorResponse.create(e, Operation.STATUS_CODE_BAD_REQUEST);
    }

    public static String toServiceErrorResponseJson(Throwable e) {
        return Utils.toJson(toServiceErrorResponse(e));
    }

    public static ServiceErrorResponse toValidationErrorResponse(Throwable t) {
        ServiceErrorResponse rsp = new ServiceErrorResponse();
        rsp.message = t.getLocalizedMessage();
        return rsp;
    }

    public static boolean isValidationError(Throwable e) {
        return e instanceof IllegalArgumentException;
    }

    public static String toHexString(byte[] data) {
        //http://stackoverflow.com/a/9855338
        char[] sb = new char[data.length * 2];
        for (int i = 0; i < data.length; i++) {
            int v = data[i] & 0xFF;
            sb[2 * i] = HEX_CHARS[v >>> 4];
            sb[2 * i + 1] = HEX_CHARS[v & 0x0F];
        }

        return new String(sb);
    }

    /**
     * Compute path to static resources for service.
     *
     * For example: the class "com.vmware.dcp.services.common.ExampleService" is converted to
     * "com/vmware/dcp/services/common/ExampleService".
     *
     * @param klass Service class
     * @return String
     */
    public static String buildServicePath(Class<? extends Service> klass) {
        return klass.getName().replace('.', '/');
    }

    /**
     * Compute URI prefix for static resources of a service.
     *
     * @param klass Service class
     * @return String
     */
    public static String buildUiResourceUriPrefixPath(Class<? extends Service> klass) {
        return UriUtils.buildUriPath(ServiceUriPaths.UI_RESOURCES, buildServicePath(klass));
    }

    /**
     * Compute URI prefix for static resources of a service.
     *
     * @param service Service
     * @return String
     */
    public static String buildUiResourceUriPrefixPath(Service service) {
        return buildUiResourceUriPrefixPath(service.getClass());
    }

    /**
     * Compute URI prefix for static resources of a service with custom UI resource path.
     *
     * @param service
     * @return String
     */
    public static String buildCustomUiResourceUriPrefixPath(Service service) {
        return UriUtils.buildUriPath(ServiceUriPaths.UI_RESOURCES,
                service.getDocumentTemplate().documentDescription.userInterfaceResourcePath);
    }

    public static Object setJsonProperty(Object body, String fieldName, String fieldValue) {
        JsonObject jo = null;
        if (body instanceof JsonObject) {
            jo = (JsonObject) body;
        } else {
            jo = new JsonParser().parse((String) body).getAsJsonObject();
        }
        jo.remove(fieldName);
        if (fieldValue != null) {
            jo.addProperty(fieldName, fieldValue);
        }

        return jo;
    }

    public static void setTimeComparisonEpsilonMicros(long micros) {
        timeComparisonEpsilonMicros = micros;
    }

    public static long getTimeComparisonEpsilonMicros() {
        return timeComparisonEpsilonMicros;
    }

    public static String validateServiceOption(EnumSet<ServiceOption> options, ServiceOption option) {
        EnumSet<ServiceOption> reqs = null;
        EnumSet<ServiceOption> antiReqs = null;
        switch (option) {
        case CONCURRENT_UPDATE_HANDLING:
            antiReqs = EnumSet.of(ServiceOption.EAGER_CONSISTENCY, ServiceOption.OWNER_SELECTION,
                    ServiceOption.STRICT_UPDATE_CHECKING);
            break;
        case EAGER_CONSISTENCY:
            reqs = EnumSet.of(ServiceOption.REPLICATION, ServiceOption.OWNER_SELECTION);
            antiReqs = EnumSet.of(ServiceOption.CONCURRENT_UPDATE_HANDLING);
            break;
        case OWNER_SELECTION:
            reqs = EnumSet.of(ServiceOption.REPLICATION);
            antiReqs = EnumSet.of(ServiceOption.CONCURRENT_UPDATE_HANDLING);
            break;
        case STRICT_UPDATE_CHECKING:
            antiReqs = EnumSet.of(ServiceOption.CONCURRENT_UPDATE_HANDLING);
            break;
        case PERIODIC_MAINTENANCE:
            break;
        case PERSISTENCE:
            break;
        case REPLICATION:
            break;
        case DOCUMENT_OWNER:
            break;
        case IDEMPOTENT_POST:
            break;
        case FACTORY:
            break;
        case FACTORY_ITEM:
            break;
        case HTML_USER_INTERFACE:
            break;
        case INSTRUMENTATION:
            break;
        case LIFO_QUEUE:
            break;
        case NONE:
            break;
        case UTILITY:
            break;
        default:
            break;
        }

        if (!options.contains(option)) {
            return null;
        }

        if (reqs == null && antiReqs == null) {
            return null;
        }

        if (reqs != null) {
            EnumSet<ServiceOption> missingReqs = EnumSet.noneOf(ServiceOption.class);
            for (ServiceOption r : reqs) {
                if (!options.contains(r)) {
                    missingReqs.add(r);
                }
            }

            if (!missingReqs.isEmpty()) {
                String error = String.format("%s missing required options: %s", option, missingReqs);
                return error;
            }
        }

        if (antiReqs != null) {
            EnumSet<ServiceOption> conflictReqs = EnumSet.noneOf(ServiceOption.class);
            for (ServiceOption r : antiReqs) {
                if (options.contains(r)) {
                    conflictReqs.add(r);
                }
            }

            if (!conflictReqs.isEmpty()) {
                String error = String.format("%s conflicts with options: %s", option, conflictReqs);
                return error;
            }
        }

        return null;
    }

    public static String getOsName(SystemHostInfo systemInfo) {
        return systemInfo.properties.get(SystemHostInfo.PROPERTY_NAME_OS_NAME);
    }

    public static OsFamily determineOsFamily(String osName) {
        osName = osName == null ? "" : osName.toLowerCase(Locale.ENGLISH);
        if (osName.contains("mac")) {
            return OsFamily.MACOS;
        } else if (osName.contains("win")) {
            return OsFamily.WINDOWS;
        } else if (osName.contains("nux")) {
            return OsFamily.LINUX;
        } else {
            return OsFamily.OTHER;
        }
    }

    /**
     * An alternative to {@link InetAddress#isReachable(int)} which accounts for the Windows
     * implementation of that method NOT using ICMP. This method invokes the "ping" command
     * installed in all Windows implementations since Windows XP. For other operating systems it
     * will fall back on the default implementation of the original method.
     */
    public static boolean isReachable(SystemHostInfo systemInfo, InetAddress addr, long timeoutMs)
            throws IOException {
        if (systemInfo.osFamily == OsFamily.WINDOWS) {
            // windows -> delegate to "ping"
            return isReachableByPing(systemInfo, addr, timeoutMs);
        }

        // non-windows -> fallback on default impl
        return addr.isReachable((int) timeoutMs);
    }

    public static boolean isReachableByPing(SystemHostInfo systemInfo, InetAddress addr, long timeoutMs)
            throws IOException {
        try {
            Process process = new ProcessBuilder("ping", "-n", "1", "-w", Long.toString(timeoutMs),
                    getNormalizedHostAddress(systemInfo, addr)).start();
            boolean completed = process.waitFor(PING_LAUNCH_TOLERANCE_MS + timeoutMs, TimeUnit.MILLISECONDS);
            return completed && process.exitValue() == 0;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    /**
     * An alternative to {@link InetAddress#getHostAddress()} that formats particular types of IP
     * address in more universal formats.
     *
     * Specifically, Java formats link-local IPv6 addresses in Linux-friendly manner:
     * {@code <address>%<interface_name>} e.g. {@code fe80:0:0:0:5971:14f6:c8ac:9e8f%eth0}. However,
     * Windows requires a different format for such addresses: {@code <address>%<numeric_scope_id>}
     * e.g. {@code fe80:0:0:0:5971:14f6:c8ac:9e8f%34}. This method {@link #isWindowsHost detects if
     * the caller is a Windows host} and will adjust the host address accordingly.
     *
     * Otherwise, this will delegate to the original method.
     */
    public static String getNormalizedHostAddress(SystemHostInfo systemInfo, InetAddress addr) {
        String addrStr = addr.getHostAddress();

        // does it require special treatment?
        if (systemInfo.osFamily == OsFamily.WINDOWS && addr instanceof Inet6Address && addr.isLinkLocalAddress()) {
            // Inet6Address appends the intf name, rather than numeric id -> remedying
            Inet6Address ip6Addr = (Inet6Address) addr;
            int pct = addrStr.lastIndexOf('%');
            if (pct > -1) {
                addrStr = addrStr.substring(0, pct) + '%' + ip6Addr.getScopeId();
            }
        }

        return addrStr;
    }

    public static byte[] encodeBody(Operation op) throws Throwable {
        byte[] data = null;
        String contentType = op.getContentType();

        if (!op.hasBody()) {
            op.setContentLength(0);
            return null;
        }

        Object body = op.getBodyRaw();
        if (body instanceof String) {
            data = ((String) body).getBytes(Utils.CHARSET);
        } else if (body instanceof byte[]) {
            data = (byte[]) body;
            if (contentType == null) {
                op.setContentType(Operation.MEDIA_TYPE_APPLICATION_OCTET_STREAM);
            }
        }

        if (data == null) {
            if (contentType == null || contentType.contains(Operation.MEDIA_TYPE_APPLICATION_JSON)) {
                String encodedBody = null;
                if (op.getAction() == Action.GET) {
                    encodedBody = Utils.toJsonHtml(body);
                } else {
                    encodedBody = Utils.toJson(body);
                    if (contentType == null) {
                        op.setContentType(Operation.MEDIA_TYPE_APPLICATION_JSON);
                    }
                }
                data = encodedBody.getBytes(Utils.CHARSET);
            } else {
                throw new IllegalArgumentException("Unrecognized content type: " + contentType);
            }
        }

        op.setContentLength(data.length);
        return data;
    }

    public static void decodeBody(Operation op, ByteBuffer buffer) {
        Object body = null;

        if (op.getContentLength() == 0) {
            op.setContentType(Operation.MEDIA_TYPE_APPLICATION_JSON).complete();
            return;
        }

        try {
            String contentType = op.getContentType();
            body = decodeIfText(buffer, contentType);
            if (body == null) {
                // unrecognized or binary body, use the raw bytes
                byte[] data = new byte[(int) op.getContentLength()];
                buffer.get(data);
                body = data;
            }
            op.setBodyNoCloning(body).complete();
        } catch (Throwable e) {
            op.fail(e);
        }
    }

    public static MessageDigest createDigest() {
        try {
            return MessageDigest.getInstance(DEFAULT_CONTENT_HASH);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static String decodeIfText(ByteBuffer buffer, String contentType) throws CharacterCodingException {
        String body = null;
        if (contentType == null) {
            return null;
        }

        if (contentType.contains(Operation.MEDIA_TYPE_APPLICATION_JSON) || contentType.contains("text")
                || contentType.contains("css") || contentType.contains("script") || contentType.contains("html")
                || contentType.contains("xml")) {
            body = Charset.forName(Utils.CHARSET).newDecoder().decode(buffer).toString();
        }

        return body;
    }

    /**
     * Compute ui resource path for this service.
     * <p>
     * If service has defined the custom path on ServiceDocumentDescription
     * userInterfaceResourcePath field that will be used  else default UI path
     * will be calculated using service path Eg. for ExampleService
     * default path will be ui/com/vmware/dcp/services/common/ExampleService
     *
     * @param type service class for which UI path has to be extracted
     * @return UI resource path object
     */
    public static Path getServiceUiResourcePath(Service s) {
        ServiceDocument sd = s.getDocumentTemplate();
        ServiceDocumentDescription sdd = sd.documentDescription;
        if (sdd != null && sdd.userInterfaceResourcePath != null) {
            String resourcePath = sdd.userInterfaceResourcePath;
            if (!resourcePath.isEmpty()) {
                return Paths.get(resourcePath);
            } else {
                log(Utils.class, Utils.class.getSimpleName(), Level.SEVERE,
                        "UserInterface resource path field empty for service document %s",
                        s.getClass().getSimpleName());
            }
        } else {
            String servicePath = buildServicePath(s.getClass());
            return Paths.get(UI_DIRECTORY_NAME, servicePath);
        }
        return null;
    }

    /**
     * Atomically returns a map element for the specifying key. A new instance of value is created if it is
     * missing. This may be efficiently used for creating map of maps/sets.
     * <p/>
     * This method is thread-safe.
     *
     * @param map  Map to take value from.
     * @param key  Key to use.
     * @param ctor Value constructor. This constructor may be invoked multiple times for the same value, but
     *             only one value is returned.
     * @param <K>  Map key type.
     * @param <V>  Map value type.
     * @return new or existing element value.
     */
    public static <K, V> V atomicGetOrCreate(ConcurrentMap<K, V> map, K key, Callable<V> ctor) {
        V value = map.get(key);
        if (value == null) {
            try {
                value = ctor.call();
            } catch (Exception e) {
                throw new RuntimeException("Element constructor should now throw an exception", e);
            }
            V existing = map.putIfAbsent(key, value);
            if (existing != null) {
                return existing;
            }
        }
        return value;
    }

    /**
     * Merges {@code patch} object into the {@code source} object by replacing all {@code source} fields with non-null
     * {@code patch} fields. Only fields with specified merge policy are merged.
     *
     * @param desc Service document description.
     * @param source Source object.
     * @param patch  Patch object.
     * @param <T>    Object type.
     * @return {@code true} in case there was at least one update. Updates of fields to same values are not considered
     *      as updates).
     * @see ServiceDocumentDescription.PropertyUsageOption
     */
    public static <T extends ServiceDocument> boolean mergeWithState(ServiceDocumentDescription desc, T source,
            T patch) {
        Class<? extends ServiceDocument> clazz = source.getClass();
        if (!patch.getClass().equals(clazz)) {
            throw new IllegalArgumentException("Source object and patch object types mismatch");
        }
        boolean modified = false;
        for (PropertyDescription prop : desc.propertyDescriptions.values()) {
            if (prop.usageOptions != null
                    && prop.usageOptions.contains(PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL)) {
                Object o = ReflectionUtils.getPropertyValue(prop, patch);
                if (o != null) {
                    if (!o.equals(ReflectionUtils.getPropertyValue(prop, source))) {
                        ReflectionUtils.setPropertyValue(prop, source, o);
                        modified = true;
                    }
                }
            }
        }
        return modified;
    }

    /**
     * Merges a list of @ServiceDocumentQueryResult that were already <b>sorted</b> on <i>documentLink</i>.
     * The merge will be done in linear time.
     *
     * @param dataSources A list of @ServiceDocumentQueryResult <b>sorted</b> on <i>documentLink</i>.
     * @param isAscOrder  Whether the document links are sorted in ascending order.
     * @return The merging result.
     */
    public static ServiceDocumentQueryResult mergeQueryResults(List<ServiceDocumentQueryResult> dataSources,
            boolean isAscOrder) {

        // To hold the merge result.
        ServiceDocumentQueryResult result = new ServiceDocumentQueryResult();
        result.documents = new HashMap<>();
        result.documentCount = 0L;

        // For each list of documents to be merged, a pointer is maintained to indicate which element
        // is to be merged. The initial values are 0s.
        int[] indices = new int[dataSources.size()];

        // Keep going until the last element in each list has been merged.
        while (true) {
            // Always pick the document link that is the smallest or largest depending on "isAscOrder" from
            // all lists to be merged. "documentLinkPicked" is used to keep the winner.
            String documentLinkPicked = null;

            // Ties could happen among the lists. That is, multiple elements could be picked in one iteration,
            // and the lists where they locate need to be recorded so that their pointers could be adjusted accordingly.
            List<Integer> sourcesPicked = new ArrayList<>();

            // In each iteration, the current elements in all lists need to be compared to pick the winners.
            for (int i = 0; i < dataSources.size(); i++) {
                // If the current list still have elements left to be merged, then proceed.
                if (indices[i] < dataSources.get(i).documentCount) {
                    String documentLink = dataSources.get(i).documentLinks.get(indices[i]);
                    if (documentLinkPicked == null) {
                        // No document link has been picked in this iteration, so it is the winner at the current time.
                        documentLinkPicked = documentLink;
                        sourcesPicked.add(i);
                    } else {
                        if (isAscOrder && documentLink.compareTo(documentLinkPicked) < 0
                                || !isAscOrder && documentLink.compareTo(documentLinkPicked) > 0) {
                            // If this document link is smaller or bigger (depending on isAscOrder),
                            // then replace the original winner.
                            documentLinkPicked = documentLink;
                            sourcesPicked.clear();
                            sourcesPicked.add(i);
                        } else if (documentLink.equals(documentLinkPicked)) {
                            // If it is a tie, we will need to record this element too so that
                            // it won't be processed in the next iteration.
                            sourcesPicked.add(i);
                        }
                    }
                }
            }

            if (documentLinkPicked != null) {
                // Save the winner to the result.
                result.documentLinks.add(documentLinkPicked);
                result.documents.put(documentLinkPicked,
                        dataSources.get(sourcesPicked.get(0)).documents.get(documentLinkPicked));
                result.documentCount++;

                // Move the pointer of the lists where the winners locate.
                for (int i : sourcesPicked) {
                    indices[i]++;
                }
            } else {
                // No document was picked, that means all lists had been processed,
                // and the merging work is done.
                break;
            }
        }

        return result;
    }
}