| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.tinkerpop.gremlin.structure.io.gryo.kryoshim; |
| |
| import org.apache.commons.configuration.Configuration; |
| import org.apache.tinkerpop.shaded.kryo.io.Input; |
| import org.apache.tinkerpop.shaded.kryo.io.Output; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.ServiceLoader; |
| |
| /** |
| * Loads the highest-priority or user-selected {@link KryoShimService}. |
| */ |
| public class KryoShimServiceLoader { |
| |
| private static volatile KryoShimService cachedShimService; |
| |
| private static volatile Configuration conf; |
| |
| private static final Logger log = LoggerFactory.getLogger(KryoShimServiceLoader.class); |
| |
| /** |
| * Set this system property to the fully-qualified name of a {@link KryoShimService} |
| * package-and-classname to force it into service. Setting this property causes the |
| * priority-selection mechanism ({@link KryoShimService#getPriority()}) to be ignored. |
| */ |
| public static final String KRYO_SHIM_SERVICE = "gremlin.io.kryoShimService"; |
| |
| public static void applyConfiguration(final Configuration conf) { |
| KryoShimServiceLoader.conf = conf; |
| load(true); |
| } |
| |
| /** |
| * Return a reference to the shim service. This method may return a cached shim service |
| * unless {@code forceReload} is true. Calls to this method need not be externally |
| * synchonized. |
| * |
| * @param forceReload if false, this method may use its internal service cache; if true, |
| * this method must ignore cache, and it must invoke {@link ServiceLoader#reload()} |
| * before selecting a new service to return |
| * @return the shim service |
| */ |
| public static KryoShimService load(final boolean forceReload) { |
| |
| if (null != cachedShimService && !forceReload) { |
| return cachedShimService; |
| } |
| |
| final ArrayList<KryoShimService> services = new ArrayList<>(); |
| |
| final ServiceLoader<KryoShimService> sl = ServiceLoader.load(KryoShimService.class); |
| |
| KryoShimService result = null; |
| |
| synchronized (KryoShimServiceLoader.class) { |
| if (forceReload) { |
| sl.reload(); |
| } |
| |
| for (KryoShimService kss : sl) { |
| services.add(kss); |
| } |
| } |
| |
| String shimClass = System.getProperty(KRYO_SHIM_SERVICE); |
| |
| if (null != shimClass) { |
| for (KryoShimService kss : services) { |
| if (kss.getClass().getCanonicalName().equals(shimClass)) { |
| log.info("Set {} provider to {} ({}) from system property {}={}", |
| KryoShimService.class.getSimpleName(), kss, kss.getClass(), |
| KRYO_SHIM_SERVICE, shimClass); |
| result = kss; |
| } |
| } |
| } else { |
| Collections.sort(services, KryoShimServiceComparator.INSTANCE); |
| |
| for (KryoShimService kss : services) { |
| log.debug("Found Kryo shim service class {} (priority {})", kss.getClass(), kss.getPriority()); |
| } |
| |
| if (0 != services.size()) { |
| result = services.get(services.size() - 1); |
| } |
| } |
| |
| |
| if (null == result) { |
| throw new IllegalStateException("Unable to load KryoShimService"); |
| } |
| |
| log.info("Set {} provider to {} ({}) because its priority value ({}) is the highest available", |
| KryoShimService.class.getSimpleName(), result, result.getClass(), result.getPriority()); |
| |
| final Configuration userConf = conf; |
| |
| if (null != userConf) { |
| log.info("Configuring {} provider {} with user-provided configuration", |
| KryoShimService.class.getSimpleName(), result); |
| result.applyConfiguration(userConf); |
| } |
| |
| return cachedShimService = result; |
| } |
| |
| /** |
| * Equivalent to {@link #load(boolean)} with the parameter {@code true}. |
| * |
| * @return the (possibly cached) shim service |
| */ |
| public static KryoShimService load() { |
| return load(false); |
| } |
| |
| /** |
| * A loose abstraction of {@link org.apache.tinkerpop.shaded.kryo.Kryo#writeClassAndObject(Output, Object)}, |
| * where the {@code output} parameter is an internally-created {@link ByteArrayOutputStream}. Returns |
| * the byte array underlying that stream. |
| * |
| * @param o an object for which the instance and class are serialized |
| * @return the serialized form |
| */ |
| public static byte[] writeClassAndObjectToBytes(final Object o) { |
| final KryoShimService shimService = load(); |
| |
| final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| |
| shimService.writeClassAndObject(o, baos); |
| |
| return baos.toByteArray(); |
| } |
| |
| /** |
| * A loose abstraction of {@link org.apache.tinkerpop.shaded.kryo.Kryo#readClassAndObject(Input)}, |
| * where the {@code input} parameter is {@code source}. Returns the deserialized object. |
| * |
| * @param source an input stream containing data for a serialized object class and instance |
| * @param <T> the type to which the deserialized object is cast as it is returned |
| * @return the deserialized object |
| */ |
| public static <T> T readClassAndObject(final InputStream source) { |
| final KryoShimService shimService = load(); |
| |
| return (T) shimService.readClassAndObject(source); |
| } |
| |
| /** |
| * Selects the service with greatest {@link KryoShimService#getPriority()} |
| * (not absolute value). |
| * <p> |
| * Breaks ties with lexicographical comparison of classnames where the |
| * name that sorts last is considered to have highest priority. Ideally |
| * nothing should rely on that tiebreaking behavior, but it beats random |
| * selection in case a user ever gets into that situation by accident and |
| * tries to figure out what's going on. |
| */ |
| private enum KryoShimServiceComparator implements Comparator<KryoShimService> { |
| INSTANCE; |
| |
| @Override |
| public int compare(final KryoShimService a, final KryoShimService b) { |
| final int ap = a.getPriority(); |
| final int bp = b.getPriority(); |
| |
| if (ap < bp) { |
| return -1; |
| } else if (bp < ap) { |
| return 1; |
| } else { |
| final int result = a.getClass().getCanonicalName().compareTo(b.getClass().getCanonicalName()); |
| |
| if (0 == result) { |
| log.warn("Found two {} implementations with the same canonical classname: {}. " + |
| "This may indicate a problem with the classpath/classloader such as " + |
| "duplicate or conflicting copies of the file " + |
| "META-INF/services/org.apache.tinkerpop.gremlin.structure.io.gryo.kryoshim.KryoShimService.", |
| a.getClass().getCanonicalName()); |
| } else { |
| final String winner = 0 < result ? a.getClass().getCanonicalName() : b.getClass().getCanonicalName(); |
| log.warn("{} implementations {} and {} are tied with priority value {}. " + |
| "Preferring {} to the other because it has a lexicographically greater classname. " + |
| "Consider setting the system property \"{}\" instead of relying on priority tie-breaking.", |
| KryoShimService.class.getSimpleName(), a, b, ap, winner, KRYO_SHIM_SERVICE); |
| } |
| |
| return result; |
| } |
| } |
| } |
| } |