blob: 4227edc542f4b7bfcfdf406d3be9971fb8f63668 [file] [log] [blame]
/*
* 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;
}
}
}
}