blob: 05e30d7b343c45550ac898d1dafe2ad9550b956c [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.fury.config;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.apache.fury.Fury;
import org.apache.fury.ThreadLocalFury;
import org.apache.fury.ThreadSafeFury;
import org.apache.fury.logging.Logger;
import org.apache.fury.logging.LoggerFactory;
import org.apache.fury.pool.ThreadPoolFury;
import org.apache.fury.resolver.ClassResolver;
import org.apache.fury.serializer.JavaSerializer;
import org.apache.fury.serializer.ObjectStreamSerializer;
import org.apache.fury.serializer.Serializer;
import org.apache.fury.serializer.TimeSerializers;
import org.apache.fury.serializer.collection.GuavaCollectionSerializers;
import org.apache.fury.util.GraalvmSupport;
import org.apache.fury.util.Platform;
/** Builder class to config and create {@link Fury}. */
// Method naming style for this builder:
// - withXXX: withCodegen
// - verbXXX: requireClassRegistration
@SuppressWarnings("rawtypes")
public final class FuryBuilder {
private static final Logger LOG = LoggerFactory.getLogger(FuryBuilder.class);
private static final boolean ENABLE_CLASS_REGISTRATION_FORCIBLY;
static {
String flagValue =
System.getProperty(
"fury.enable_fury_security_mode_forcibly",
System.getenv("ENABLE_CLASS_REGISTRATION_FORCIBLY"));
ENABLE_CLASS_REGISTRATION_FORCIBLY = "true".equals(flagValue) || "1".equals(flagValue);
}
boolean checkClassVersion = false;
Language language = Language.JAVA;
boolean trackingRef = false;
boolean basicTypesRefIgnored = true;
boolean stringRefIgnored = true;
boolean timeRefIgnored = true;
ClassLoader classLoader;
boolean compressInt = true;
public LongEncoding longEncoding = LongEncoding.SLI;
boolean compressString = true;
CompatibleMode compatibleMode = CompatibleMode.SCHEMA_CONSISTENT;
boolean checkJdkClassSerializable = true;
Class<? extends Serializer> defaultJDKStreamSerializerType = ObjectStreamSerializer.class;
boolean requireClassRegistration = true;
boolean shareMetaContext = false;
boolean codeGenEnabled = true;
boolean deserializeUnexistedClass = false;
boolean asyncCompilationEnabled = false;
boolean registerGuavaTypes = true;
boolean scalaOptimizationEnabled = false;
boolean suppressClassRegistrationWarnings = true;
public FuryBuilder() {}
/**
* Whether cross-language serialize the object. If you used fury for java only, please set
* language to {@link Language#JAVA}, which will have much better performance.
*/
public FuryBuilder withLanguage(Language language) {
this.language = language;
return this;
}
/** Whether track shared or circular references. */
public FuryBuilder withRefTracking(boolean trackingRef) {
this.trackingRef = trackingRef;
return this;
}
/** Whether ignore basic types shared reference. */
public FuryBuilder ignoreBasicTypesRef(boolean ignoreBasicTypesRef) {
this.basicTypesRefIgnored = ignoreBasicTypesRef;
return this;
}
/** Whether ignore string shared reference. */
public FuryBuilder ignoreStringRef(boolean ignoreStringRef) {
this.stringRefIgnored = ignoreStringRef;
return this;
}
/**
* Whether ignore reference tracking of all time types registered in {@link TimeSerializers} when
* ref tracking is enabled.
*
* @see Config#isTimeRefIgnored
*/
public FuryBuilder ignoreTimeRef(boolean ignoreTimeRef) {
this.timeRefIgnored = ignoreTimeRef;
return this;
}
/** Use variable length encoding for int/long. */
public FuryBuilder withNumberCompressed(boolean numberCompressed) {
this.compressInt = numberCompressed;
withLongCompressed(numberCompressed);
return this;
}
/** Use variable length encoding for int. */
public FuryBuilder withIntCompressed(boolean intCompressed) {
this.compressInt = intCompressed;
return this;
}
/**
* Use variable length encoding for long. Enabled by default, use {@link LongEncoding#SLI} (Small
* long as int) for long encoding.
*/
public FuryBuilder withLongCompressed(boolean longCompressed) {
return withLongCompressed(longCompressed ? LongEncoding.SLI : LongEncoding.LE_RAW_BYTES);
}
/** Use variable length encoding for long. */
public FuryBuilder withLongCompressed(LongEncoding longEncoding) {
this.longEncoding = Objects.requireNonNull(longEncoding);
return this;
}
/** Whether compress string for small size. */
public FuryBuilder withStringCompressed(boolean stringCompressed) {
this.compressString = stringCompressed;
return this;
}
/**
* Set classloader for fury to load classes, this classloader can't up updated. Fury will cache
* the class meta data, if classloader can be updated, there may be class meta collision if
* different classloaders have classes with same name.
*
* <p>If you want to change classloader, please use {@link org.apache.fury.util.LoaderBinding} or
* {@link ThreadSafeFury} to setup mapping between classloaders and fury instances.
*/
public FuryBuilder withClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
return this;
}
/**
* Set class schema compatible mode.
*
* @see CompatibleMode
*/
public FuryBuilder withCompatibleMode(CompatibleMode compatibleMode) {
this.compatibleMode = compatibleMode;
return this;
}
/**
* Whether check class schema consistency, will be disabled automatically when {@link
* CompatibleMode#COMPATIBLE} is enabled. Do not disable this option unless you can ensure the
* class won't evolve.
*/
public FuryBuilder withClassVersionCheck(boolean checkClassVersion) {
this.checkClassVersion = checkClassVersion;
return this;
}
/** Whether check classes under `java.*` implement {@link java.io.Serializable}. */
public FuryBuilder withJdkClassSerializableCheck(boolean checkJdkClassSerializable) {
this.checkJdkClassSerializable = checkJdkClassSerializable;
return this;
}
/**
* Whether pre-register guava types such as `RegularImmutableMap`/`RegularImmutableList`. Those
* types are not public API, but seems pretty stable.
*
* @see GuavaCollectionSerializers
*/
public FuryBuilder registerGuavaTypes(boolean register) {
this.registerGuavaTypes = register;
return this;
}
/**
* Whether to require registering classes for serialization, enabled by default. If disabled,
* unknown classes can be deserialized, which may be insecure and cause remote code execution
* attack if the classes `constructor`/`equals`/`hashCode` method contain malicious code. Do not
* disable class registration if you can't ensure your environment are *indeed secure*. We are not
* responsible for security risks if you disable this option. If you disable this option, you can
* configure {@link org.apache.fury.resolver.ClassChecker} by {@link
* ClassResolver#setClassChecker} to control which classes are allowed being serialized.
*/
public FuryBuilder requireClassRegistration(boolean requireClassRegistration) {
this.requireClassRegistration = requireClassRegistration;
return this;
}
/**
* Whether suppress class registration warnings. The warnings can be used for security audit, but
* may be annoying, this suppression will be enabled by default.
*
* @see Config#suppressClassRegistrationWarnings()
*/
public FuryBuilder suppressClassRegistrationWarnings(boolean suppress) {
this.suppressClassRegistrationWarnings = suppress;
return this;
}
/** Whether to enable meta share mode. */
public FuryBuilder withMetaContextShare(boolean shareMetaContext) {
this.shareMetaContext = shareMetaContext;
return this;
}
/**
* Whether deserialize/skip data of un-existed class.
*
* @see Config#deserializeUnexistedClass()
*/
public FuryBuilder withDeserializeUnexistedClass(boolean deserializeUnexistedClass) {
this.deserializeUnexistedClass = deserializeUnexistedClass;
return this;
}
/**
* Whether enable jit for serialization. When disabled, the first serialization will be faster
* since no need to generate code, but later will be much slower compared jit mode.
*/
public FuryBuilder withCodegen(boolean codeGen) {
this.codeGenEnabled = codeGen;
return this;
}
/**
* Whether enable async compilation. If enabled, serialization will use interpreter mode
* serialization first and switch to jit serialization after async serializer jit for a class \ is
* finished.
*
* <p>This option will be disabled automatically for graalvm native image since graalvm native
* image doesn't support JIT at the image run time.
*
* @see Config#isAsyncCompilationEnabled()
*/
public FuryBuilder withAsyncCompilation(boolean asyncCompilation) {
this.asyncCompilationEnabled = asyncCompilation;
return this;
}
/** Whether enable scala-specific serialization optimization. */
public FuryBuilder withScalaOptimizationEnabled(boolean enableScalaOptimization) {
this.scalaOptimizationEnabled = enableScalaOptimization;
return this;
}
private void finish() {
if (classLoader == null) {
classLoader = Thread.currentThread().getContextClassLoader();
}
if (language != Language.JAVA) {
stringRefIgnored = false;
}
if (ENABLE_CLASS_REGISTRATION_FORCIBLY) {
if (!requireClassRegistration) {
LOG.warn("Class registration is enabled forcibly.");
requireClassRegistration = true;
}
}
if (defaultJDKStreamSerializerType == JavaSerializer.class) {
LOG.warn(
"JDK serialization is used for types which customized java serialization by "
+ "implementing methods such as writeObject/readObject. This is not secure, try to "
+ "use {} instead, or implement a custom {}.",
ObjectStreamSerializer.class,
Serializer.class);
}
if (compatibleMode == CompatibleMode.COMPATIBLE) {
checkClassVersion = false;
}
if (!requireClassRegistration) {
LOG.warn(
"Class registration isn't forced, unknown classes can be deserialized. "
+ "If the environment isn't secure, please enable class registration by "
+ "`FuryBuilder#requireClassRegistration(true)` or configure ClassChecker by "
+ "`ClassResolver#setClassChecker`");
}
if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE && asyncCompilationEnabled) {
LOG.info("Use sync compilation for graalvm native image since it doesn't support JIT.");
asyncCompilationEnabled = false;
}
}
/**
* Create Fury and print exception when failed. Many application will create fury as a static
* variable, Fury creation exception will be swallowed by {@link NoClassDefFoundError}. We print
* exception explicitly for better debugging.
*/
private static Fury newFury(FuryBuilder builder, ClassLoader classLoader) {
try {
return new Fury(builder, classLoader);
} catch (Throwable t) {
t.printStackTrace();
LOG.error("Fury creation failed with classloader {}", classLoader);
Platform.throwException(t);
throw new RuntimeException(t);
}
}
public Fury build() {
finish();
ClassLoader loader = this.classLoader;
// clear classLoader to avoid `LoaderBinding#furyFactory` lambda capture classLoader by
// capturing `FuryBuilder`, which make `classLoader` not able to be gc.
this.classLoader = null;
return newFury(this, loader);
}
/** Build thread safe fury. */
public ThreadSafeFury buildThreadSafeFury() {
return buildThreadLocalFury();
}
/** Build thread safe fury backed by {@link ThreadLocalFury}. */
public ThreadLocalFury buildThreadLocalFury() {
finish();
ClassLoader loader = this.classLoader;
// clear classLoader to avoid `LoaderBinding#furyFactory` lambda capture classLoader by
// capturing `FuryBuilder`, which make `classLoader` not able to be gc.
this.classLoader = null;
ThreadLocalFury threadSafeFury = new ThreadLocalFury(classLoader -> newFury(this, classLoader));
threadSafeFury.setClassLoader(loader);
return threadSafeFury;
}
/**
* Build pooled ThreadSafeFury.
*
* @param minPoolSize min pool size
* @param maxPoolSize max pool size
* @return ThreadSafeFuryPool
*/
public ThreadSafeFury buildThreadSafeFuryPool(int minPoolSize, int maxPoolSize) {
return buildThreadSafeFuryPool(minPoolSize, maxPoolSize, 30L, TimeUnit.SECONDS);
}
/**
* Build pooled ThreadSafeFury.
*
* @param minPoolSize min pool size
* @param maxPoolSize max pool size
* @param expireTime cache expire time, default 5's
* @param timeUnit TimeUnit, default SECONDS
* @return ThreadSafeFuryPool
*/
public ThreadSafeFury buildThreadSafeFuryPool(
int minPoolSize, int maxPoolSize, long expireTime, TimeUnit timeUnit) {
if (minPoolSize < 0 || maxPoolSize < 0 || minPoolSize > maxPoolSize) {
throw new IllegalArgumentException(
String.format(
"thread safe fury pool's init pool size error, please check it, min:[%s], max:[%s]",
minPoolSize, maxPoolSize));
}
finish();
ClassLoader loader = this.classLoader;
this.classLoader = null;
ThreadSafeFury threadSafeFury =
new ThreadPoolFury(
classLoader -> newFury(this, classLoader),
minPoolSize,
maxPoolSize,
expireTime,
timeUnit);
threadSafeFury.setClassLoader(loader);
return threadSafeFury;
}
}