想要并行运行非线程安全库 – 可以使用多个类加载器来完成吗?

我正在开发一个项目,在这个项目中,我们使用一个不保证线程安全(并且不是)的库和Java 8流场景中的单线程,它可以按预期工作。

我们希望使用并行流来获得低悬挂可扩展性的成果。

不幸的是,这导致库失败 – 很可能是因为一个实例干扰了与另一个实例共享的变量 – 因此我们需要隔离。

我正在考虑为每个实例(可能是本地线程)使用单独的类加载器,据我所知,这应该意味着,出于所有实际目的,我需要隔离,但我不熟悉为此目的故意构建类加载器。

这是正确的方法吗? 为了获得适当的生产质量,我该怎么做?


编辑:我被要求提供有关触发问题的情况的其他信息,以便更好地理解它。 问题仍然是关于一般情况,而不是修复图书馆。

我可以完全控制库创建的对象( https://github.com/veraPDF/ )

 org.verapdf validation-model 1.1.6  

使用项目maven存储库来存在工件。

    true  vera-dev Vera development http://artifactory.openpreservation.org/artifactory/vera-dev   

目前,强化图书馆是不可行的。


编辑:我被要求显示代码。 我们的核心适配器大致是:

 public class VeraPDFValidator implements Function { private String flavorId; private Boolean prettyXml; public VeraPDFValidator(String flavorId, Boolean prettyXml) { this.flavorId = flavorId; this.prettyXml = prettyXml; VeraGreenfieldFoundryProvider.initialise(); } @Override public byte[] apply(InputStream inputStream) { try { return apply0(inputStream); } catch (RuntimeException e) { throw e; } catch (ModelParsingException | ValidationException | JAXBException | EncryptedPdfException e) { throw new RuntimeException("invoking VeraPDF validation", e); } } private byte[] apply0(InputStream inputStream) throws ModelParsingException, ValidationException, JAXBException, EncryptedPdfException { PDFAFlavour flavour = PDFAFlavour.byFlavourId(flavorId); PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false); PDFAParser loader = Foundries.defaultInstance().createParser(inputStream, flavour); ValidationResult result = validator.validate(loader); // do in-memory generation of XML byte array - as we need to pass it to Fedora we need it to fit in memory anyway. ByteArrayOutputStream baos = new ByteArrayOutputStream(); XmlSerialiser.toXml(result, baos, prettyXml, false); final byte[] byteArray = baos.toByteArray(); return byteArray; } } 

这是一个从InputStream(提供PDF文件)映射到字节数组(表示XML报告输出)的函数。

(看到代码,我注意到构造函数中有一个初始化程序的调用,在我的特定情况下可能是这里的罪魁祸首。我仍然喜欢通用问题的解决方案。

我们面临着类似的挑战。 问题通常来自静态属性,这些属性在各个线程之间变得不情愿地“共享”。

只要我们能够保证静态属性实际上是由类加载器加载的类设置的,那么使用不同的类加载器对我们起作用。 Java 可能有一些类提供不在线程之间隔离的属性或方法,或者不是线程安全的(’ System.setProperties()Security.addProvider()都可以 – 任何有关此事项的规范文档都受到欢迎btw)。

一个潜在可行且快速的解决方案 – 至少可以让你有机会为你的库测试这个理论 – 是使用一个servlet引擎,如Jetty或Tomcat。

构建一些包含你的库的战争并并行启动进程(每场战争1次)。

在servlet线程中运行代码时,这些引擎的WebappClassLoaders尝试从父类加载器加载类(与引擎相同),如果找不到类,则尝试从包装的jar /类加载它与战争。

使用jetty,您可以通过编程方式将战争热部署到您选择的上下文中,然后根据需要在理论上扩展处理器(战争)的数量。

我们通过扩展URLClassLoader实现了我们自己的类加载器,并从Jetty Webapp ClassLoader中获得灵感。 这并不像看起来那么难。

我们的类加载器完全相反:它首先尝试从本地的jar包中加载一个类,然后尝试从父类加载器中获取它们。 这保证了从不考虑父类加载器意外加载的库(第一个)。 我们的’包’实际上是一个jar,其中包含带有自定义清单文件的其他jar /库。

按“原样”发布此类加载器代码不会有太多意义(并创建一些版权问题)。 如果你想进一步探索这条路线,我可以试着想出一个骨架。

Jetty WebappClassLoader的来源

答案实际上取决于您的图书馆依赖的内容:

  1. 如果您的库依赖于至少一个本机库,则使用ClassLoader来隔离库的代码将无济于事,因为根据JNI规范 ,不允许将相同的JNI本机库加载到多个类加载器中,以便您最终会出现UnsatisfiedLinkError
  2. 如果您的库依赖于至少一个不打算共享的外部资源(例如文件而且由您的库修改),则最终可能会出现复杂的错误和/或资源损坏。

假设您不在上面列出的情况中,一般来说,如果一个类被称为非线程安全并且不修改任何静态字段,则每个调用或每个线程使用此类的专用实例就足够了作为类实例然后不再共享。

在这里,您的库显然依赖并修改了一些不打算共享的静态字段,您确实需要在专用的ClassLoader隔离库的ClassLoader ,当然要确保您的线程不共享相同的ClassLoader

为此您可以简单地创建一个URLClassLoader ,您可以将库的位置作为URL (使用URLClassLoader.newInstance(URL[] urls, ClassLoader parent) ),然后通过reflection,您将检索与您的库对应的类。入口点并调用您的目标方法。 为避免在每次调用时构建新的URLClassLoader ,您可以考虑依赖ThreadLocal来存储URLClassLoader或要用于给定线程的ClassMethod实例。


所以这是你如何继续:

假设我的库的入口点是类Foo ,如下所示:

 package com.company; public class Foo { // A static field in which we store the name of the current thread public static String threadName; public void execute() { // We print the value of the field before setting a value System.out.printf( "%s: The value before %s%n", Thread.currentThread().getName(), threadName ); // We set a new value threadName = Thread.currentThread().getName(); // We print the value of the field after setting a value System.out.printf( "%s: The value after %s%n", Thread.currentThread().getName(), threadName ); } } 

这个类显然不是线程安全的,并且方法execute修改静态字段的值,这个静态字段的值不会被并发线程修改,就像你的用例一样。

假设要启动我的库,我只需要创建一个Foo实例并调用方法execute 。 我可以将相应的Method存储在ThreadLocal中,通过使用ThreadLocal.withInitial(Supplier supplier)作为下一个每个线程只reflection一次来检索它:

 private static final ThreadLocal TL = ThreadLocal.withInitial( () -> { try { // Create the instance of URLClassLoader using the context // CL as parent CL to be able to retrieve the potential // dependencies of your library assuming that they are // thread safe otherwise you will need to provide their // URL to isolate them too URLClassLoader cl = URLClassLoader.newInstance( new URL[]{/* Here the URL of my library*/}, Thread.currentThread().getContextClassLoader() ); // Get by reflection the class Foo Class myClass = cl.loadClass("com.company.Foo"); // Get by reflection the method execute return myClass.getMethod("execute"); } catch (Exception e) { // Here deal with the exceptions throw new IllegalStateException(e); } } ); 

最后让我们模拟我的库的并发执行:

 // Launch 50 times concurrently my library IntStream.rangeClosed(1, 50).parallel().forEach( i -> { try { // Get the method instance from the ThreadLocal Method myMethod = TL.get(); // Create an instance of my class using the default constructor Object myInstance = myMethod.getDeclaringClass().newInstance(); // Invoke the method myMethod.invoke(myInstance); } catch (Exception e) { // Here deal with the exceptions throw new IllegalStateException(e); } } ); 

您将获得下一个类型的输出,该输出显示我们在线程之间没有冲突,并且线程正确地从一次execute调用到另一次调用时重用其对应的类/字段的值:

 ForkJoinPool.commonPool-worker-7: The value before null ForkJoinPool.commonPool-worker-7: The value after ForkJoinPool.commonPool-worker-7 ForkJoinPool.commonPool-worker-7: The value before ForkJoinPool.commonPool-worker-7 ForkJoinPool.commonPool-worker-7: The value after ForkJoinPool.commonPool-worker-7 main: The value before null main: The value after main main: The value before main main: The value after main ... 

由于此方法将为每个线程创建一个ClassLoader ,因此请确保使用具有固定线程数的线程池应用此方法,并且应明智地选择线程数以防止内存耗尽,因为ClassLoader不是免费的内存占用,因此您需要根据堆大小限制实例总量。

一旦完成了库,就应该为线程池的每个线程清理ThreadLocal以防止内存泄漏,这样做是为了继续:

 // The size of your the thread pool // Here as I used for my example the common pool, its size by default is // Runtime.getRuntime().availableProcessors() int poolSize = Runtime.getRuntime().availableProcessors(); // The cyclic barrier used to make sure that all the threads of the pool // will execute the code that will cleanup the ThreadLocal CyclicBarrier barrier = new CyclicBarrier(poolSize); // Launch one cleanup task per thread in the pool IntStream.rangeClosed(1, poolSize).parallel().forEach( i -> { try { // Wait for all other threads of the pool // This is needed to fill up the thread pool in order to make sure // that all threads will execute the cleanup code barrier.await(); // Close the URLClassLoader to prevent memory leaks ((URLClassLoader) TL.get().getDeclaringClass().getClassLoader()).close(); } catch (Exception e) { // Here deal with the exceptions throw new IllegalStateException(e); } finally { // Remove the URLClassLoader instance for this thread TL.remove(); } } ); 

我找到了问题,并为您创建了一个小工具:

https://github.com/kriegaex/ThreadSafeClassLoader

目前它尚未作为Maven Central的正式版本提供,但您可以获得如下快照:

  de.scrum-master threadsafe-classloader 1.0-SNAPSHOT      true  ossrh Sonatype OSS Snapshots https://oss.sonatype.org/content/repositories/snapshots   

ThreadSafeClassLoader

它使用JCL(Jar类加载器) ,因为它已经提供了该线程其他部分讨论的类加载,对象实例化和代理生成function。 (为什么重新发明轮子?)我在顶部添加的是一个很好的界面,正是我们需要的:

 package de.scrum_master.thread_safe; import org.xeustechnologies.jcl.JarClassLoader; import org.xeustechnologies.jcl.JclObjectFactory; import org.xeustechnologies.jcl.JclUtils; import org.xeustechnologies.jcl.proxy.CglibProxyProvider; import org.xeustechnologies.jcl.proxy.ProxyProviderFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ThreadSafeClassLoader extends JarClassLoader { private static final JclObjectFactory OBJECT_FACTORY = JclObjectFactory.getInstance(); static { ProxyProviderFactory.setDefaultProxyProvider(new CglibProxyProvider()); } private final List classes = new ArrayList<>(); public static ThreadLocal create(Class... classes) { return ThreadLocal.withInitial( () -> new ThreadSafeClassLoader(classes) ); } private ThreadSafeClassLoader(Class... classes) { super(); this.classes.addAll(Arrays.asList(classes)); for (Class clazz : classes) add(clazz.getProtectionDomain().getCodeSource().getLocation()); } public  T newObject(ObjectConstructionRules rules) { rules.validate(classes); Class castTo = rules.targetType; return JclUtils.cast(createObject(rules), castTo, castTo.getClassLoader()); } private Object createObject(ObjectConstructionRules rules) { String className = rules.implementingType.getName(); String factoryMethod = rules.factoryMethod; Object[] arguments = rules.arguments; Class[] argumentTypes = rules.argumentTypes; if (factoryMethod == null) { if (argumentTypes == null) return OBJECT_FACTORY.create(this, className, arguments); else return OBJECT_FACTORY.create(this, className, arguments, argumentTypes); } else { if (argumentTypes == null) return OBJECT_FACTORY.create(this, className, factoryMethod, arguments); else return OBJECT_FACTORY.create(this, className, factoryMethod, arguments, argumentTypes); } } public static class ObjectConstructionRules { private Class targetType; private Class implementingType; private String factoryMethod; private Object[] arguments; private Class[] argumentTypes; private ObjectConstructionRules(Class targetType) { this.targetType = targetType; } public static ObjectConstructionRules forTargetType(Class targetType) { return new ObjectConstructionRules(targetType); } public ObjectConstructionRules implementingType(Class implementingType) { this.implementingType = implementingType; return this; } public ObjectConstructionRules factoryMethod(String factoryMethod) { this.factoryMethod = factoryMethod; return this; } public ObjectConstructionRules arguments(Object... arguments) { this.arguments = arguments; return this; } public ObjectConstructionRules argumentTypes(Class... argumentTypes) { this.argumentTypes = argumentTypes; return this; } private void validate(List classes) { if (implementingType == null) implementingType = targetType; if (!classes.contains(implementingType)) throw new IllegalArgumentException( "Class " + implementingType.getName() + " is not protected by this thread-safe classloader" ); } } } 

我用几个单元和集成测试测试了我的概念,其中一个展示了如何重现和解决veraPDF问题 。

现在这是使用我的特殊类加载器时代码的样子:

VeraPDFValidator类:

我们只是在我们的类中添加一个static ThreadLocal成员,告诉它将哪些类/库放入新的类加载器(每个库提一个类就足够了,随后我的工具自动识别库)。

然后通过threadSafeClassLoader.get().newObject(forTargetType(VeraPDFValidatorHelper.class))我们在线程安全的类加载器中实例化我们的帮助器类并为它创建一个代理对象,以便我们可以从外部调用它。

BTW, static boolean threadSafeMode仅用于在static boolean threadSafeMode的旧(不安全)和新(线程安全)用法之间切换,以便使负面集成测试用例可重现原始问题。

 package de.scrum_master.app; import de.scrum_master.thread_safe.ThreadSafeClassLoader; import org.verapdf.core.*; import org.verapdf.pdfa.*; import javax.xml.bind.JAXBException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.function.Function; import static de.scrum_master.thread_safe.ThreadSafeClassLoader.ObjectConstructionRules.forTargetType; public class VeraPDFValidator implements Function { public static boolean threadSafeMode = true; private static ThreadLocal threadSafeClassLoader = ThreadSafeClassLoader.create( // Add one class per artifact for thread-safe classloader: VeraPDFValidatorHelper.class, // - our own helper class PDFAParser.class, // - veraPDF core VeraGreenfieldFoundryProvider.class // - veraPDF validation-model ); private String flavorId; private Boolean prettyXml; public VeraPDFValidator(String flavorId, Boolean prettyXml) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { this.flavorId = flavorId; this.prettyXml = prettyXml; } @Override public byte[] apply(InputStream inputStream) { try { VeraPDFValidatorHelper validatorHelper = threadSafeMode ? threadSafeClassLoader.get().newObject(forTargetType(VeraPDFValidatorHelper.class)) : new VeraPDFValidatorHelper(); return validatorHelper.validatePDF(inputStream, flavorId, prettyXml); } catch (ModelParsingException | ValidationException | JAXBException | EncryptedPdfException e) { throw new RuntimeException("invoking veraPDF validation", e); } } } 

VeraPDFValidatorHelper

在这个类中,我们隔离了对损坏库的所有访问。 这里没什么特别的,只是从OP的问题中复制的代码。 这里完成的所有事情都发生在线程安全的类加载器中。

 package de.scrum_master.app; import org.verapdf.core.*; import org.verapdf.pdfa.*; import org.verapdf.pdfa.flavours.PDFAFlavour; import org.verapdf.pdfa.results.ValidationResult; import javax.xml.bind.JAXBException; import java.io.ByteArrayOutputStream; import java.io.InputStream; public class VeraPDFValidatorHelper { public byte[] validatePDF(InputStream inputStream, String flavorId, Boolean prettyXml) throws ModelParsingException, ValidationException, JAXBException, EncryptedPdfException { VeraGreenfieldFoundryProvider.initialise(); PDFAFlavour flavour = PDFAFlavour.byFlavourId(flavorId); PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false); PDFAParser loader = Foundries.defaultInstance().createParser(inputStream, flavour); ValidationResult result = validator.validate(loader); ByteArrayOutputStream baos = new ByteArrayOutputStream(); XmlSerialiser.toXml(result, baos, prettyXml, false); return baos.toByteArray(); } } 

通过在每个线程的类加载器上隔离库,您可以保证任何类的并发属性。 唯一的例外是与引导类加载器或系统类加载器显式交互的库。 可以通过reflection或Instrumentation API将类注入到这些类加载Instrumentation 。 这种function的一个例子是Mockito的内联模拟制作者,但据我所知,它并没有遭受并发约束。

使用这种行为实现类加载器并不是很困难。 最简单的解决方案是在项目中明确包含所需的jar,例如作为资源。 这样,您可以使用URLClassLoader来加载您的类:

 URL url = getClass().getClassLoader().getResource("validation-model-1.1.6.jar"); ClassLoader classLoader = new URLClassLoader(new URL[] {url}, null); 

通过引用null作为URLClassLoader (第二个参数)的超类加载器,可以保证引导类之外没有共享类。 请注意,您不能在其外部使用此创建的类加载器的任何类。 但是,如果添加第二个包含触发逻辑的类的jar,则可以提供无需reflection即可访问的入口点:

 class MyEntryPoint implements Callable { @Override public File call() { // use library code. } } 

只需将此类添加到自己的jar中,并将其作为第二个元素提供给上面的URL数组。 请注意,您不能将库类型作为返回值引用,因为此类型对于使用入口点的类加载器之外的使用者不可用。

通过将类加载器创建包装到ThreadLocal ,可以保证类加载器是单一的:

 class Unique extends ThreadLocal implements Closable { @Override protected ClassLoader initialValue() { URL validation = Unique.class.getClassLoader() .getResource("validation-model-1.1.6.jar"); URL entry = Unique.class.getClassLoader() .getResource("my-entry.jar"); return new URLClassLoader(new URL[] {validation, entry}, null); } @Override public void close() throws IOException { get().close(); // If Java 7+, avoid handle leaks. set(null); // Make class loader eligable for GC. } public File doSomethingLibrary() throws Exception { Class type = Class.forName("pkg.MyEntryPoint", false, get()); return ((Callable) type.newInstance()).call(); } } 

请注意,类加载器是昂贵的对象,即使线程继续存在,也应该在不再需要它们时取消引用。 另外,为了避免文件泄漏,您应该先关闭URLClassLoader以取消引用。

最后,为了继续使用Maven的依赖项解析并简化代码,您可以创建一个单独的Maven模块,您可以在其中定义入口点代码并声明Maven库依赖项。 打包后,使用Maven shade插件创建一个Uber jar ,其中包含您需要的一切。 这样,您只需要为URLClassLoader提供一个jar,而不需要手动确保所有(传递)依赖项。

这个答案基于我原来的“插件”评论。 它始于一个只从引导和扩展类加载器inheritance的类加载器。

 package safeLoaderPackage; import java.net.URL; import java.net.URLClassLoader; public final class SafeClassLoader extends URLClassLoader{ public SafeClassLoader(URL[] paths){ super(paths, ClassLoader.getSystemClassLoader().getParent()); } } 

这是唯一需要包含在用户类路径中的类。 此url类加载器inheritance自ClassLoader.getSystemClassLoader()的父级。 它只包括引导和扩展类加载器。 它没有用户使用的类路径的概念。

下一个

 package safeLoaderClasses; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class SecureClassLoaderPlugin  { private URL[] paths; private Class[] args; private String method; private String unsafe; public void setMethodData(final String u, final URL[] p, String m, Class[] a){ method = m; args = a; paths = p; unsafe = u; } public Collection processUnsafe(Object[][] p){ int i; BlockingQueue q; ArrayList results = new ArrayList(); try{ i = p.length; q = new ArrayBlockingQueue(i); ThreadPoolExecutor tpe = new ThreadPoolExecutor(i, i, 0, TimeUnit.NANOSECONDS, q); for(Object[] params : p) tpe.execute(new SafeRunnable(unsafe, paths, method, args, params, results)); while(tpe.getActiveCount() != 0){ Thread.sleep(10); } for(R r: results){ System.out.println(r); } tpe.shutdown(); } catch(Throwable t){ } finally{ } return results; } } 

 package safeLoaderClasses; import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import safeLoaderInterface.SafeClassLoader; class SafeRunnable  implements Runnable{ final URL[] paths; final private String unsafe; final private String method; final private Class[] args; final private Object[] processUs; final ArrayList result; SafeRunnable(String u, URL[] p, String m, Class[] a, Object[] params, ArrayList r){ unsafe = u; paths = p; method = m; args = a; processUs = params; result = r; } public void run() { Class clazz; Object instance; Method m; SafeClassLoader sl = null; try{ sl = new SafeClassLoader(paths); System.out.println(sl); clazz = sl.loadClass(unsafe); m = clazz.getMethod(method, args); instance = clazz.newInstance(); synchronized(result){ result.add((R) m.invoke(instance, processUs)); } } catch(Throwable t){ t.printStackTrace(); } finally{ try { sl.close(); } catch (IOException e) { e.printStackTrace(); } } } } 

是插件jar。 没有lambdas。 只是一个线程池执行器。 每个线程在执行后都会添加到结果列表中。

仿制药需要抛光,但我已经针对这个类进行了测试(位于不同的jar子里)

 package stackoverflow4; public final class CrazyClass { static int i = 0; public int returnInt(){ System.out.println(i); return 8/++i; } } 

这将是从一个代码连接的方式。 需要包含类加载器的路径,因为它在getParent()调用时丢失了

 private void process(final String plugin, final String unsafe, final URL[] paths) throws Exception{ Object[][] passUs = new Object[][] {{},{}, {},{}, {},{},{},{},{},{}}; URL[] pathLoader = new URL[]{new File(new String(".../safeLoader.jar")).toURI().toURL(), new File(new String(".../safeLoaderClasses.jar")).toURI().toURL()}; //instantiate the loader SafeClassLoader sl = new SafeClassLoader(pathLoader); System.out.println(sl); Class clazz = sl.loadClass("safeLoaderClasses.SecureClassLoaderPlugin"); //Instance of the class that loads the unsafe jar and launches the thread pool executor Object o = clazz.newInstance(); //Look up the method that set ups the unsafe library Method m = clazz.getMethod("setMethodData", new Class[]{unsafe.getClass(), paths.getClass(), String.class, new Class[]{}.getClass()}); //invoke it m.invoke(o, new Object[]{unsafe,paths,"returnInt", new Class[]{}}); //Look up the method that invokes the library m = clazz.getMethod("processUnsafe", new Class[]{ passUs.getClass()}); //invoke it o = m.invoke(o, passUs); //Close the loader sl.close(); } 

有多达30多个线程,它似乎工作。 该插件使用单独的类加载器,每个线程都使用自己的类加载器。 离开方法后,一切都是gc’ed。

我相信你应该在寻求解决方法之前尝试解决问题。

您始终可以在两个线程,类加载器,进程,容器,VM或计算机中运行代码。 但它们都不是理想的。

我从代码中看到了两个defaultInstance()。 实例线程安全吗? 如果没有,我们可以有两个实例吗? 是工厂还是单身人士?

第二,冲突发生的地方? 如果是关于初始化/缓存问题,应该预先加温。

最后但并非最不重要的,如果库是开源的,请将它修复并拉取请求。

“强化图书馆是不可行的”但是引入像自定义类加载器这样的血腥解决方法是可行的吗?

好。 我是第一个不喜欢回复的回复,这些回复不是对原始问题的回复。 但我真的相信修补库比引入自定义类加载器要容易得多。

阻止程序是类org.verapdf.gf.model.impl.containers.StaticContainersstatic字段可以很容易地更改为每个线程工作,如下所示。 这会影响其他六个class级

 org.verapdf.gf.model.GFModelParser org.verapdf.gf.model.factory.colors.ColorSpaceFactory org.verapdf.gf.model.impl.cos.GFCosFileSpecification org.verapdf.gf.model.impl.external.GFEmbeddedFile org.verapdf.gf.model.impl.pd.colors.GFPDSeparation org.verapdf.gf.model.tools.FileSpecificationKeysHelper 

每个线程仍然只能有一个PDFAParser 。 但是这个分支需要十分钟才能完成,并且在基本的multithreading烟雾测试中为我工作。 我测试了这个并联系了图书馆的原作者。 也许他很乐意合并,你可以保留一个Maven参考更新和mantained库。

 package org.verapdf.gf.model.impl.containers; import org.verapdf.as.ASAtom; import org.verapdf.cos.COSKey; import org.verapdf.gf.model.impl.pd.colors.GFPDSeparation; import org.verapdf.gf.model.impl.pd.util.TaggedPDFRoleMapHelper; import org.verapdf.model.pdlayer.PDColorSpace; import org.verapdf.pd.PDDocument; import org.verapdf.pdfa.flavours.PDFAFlavour; import java.util.*; public class StaticContainers { private static ThreadLocal document; private static ThreadLocal flavour; // TaggedPDF public static ThreadLocal roleMapHelper; //PBoxPDSeparation public static ThreadLocal>> separations; public static ThreadLocal> inconsistentSeparations; //ColorSpaceFactory public static ThreadLocal> cachedColorSpaces; public static ThreadLocal> fileSpecificationKeys; public static void clearAllContainers() { document = new ThreadLocal(); flavour = new ThreadLocal(); roleMapHelper = new ThreadLocal(); separations = new ThreadLocal>>(); separations.set(new HashMap>()); inconsistentSeparations = new ThreadLocal>(); inconsistentSeparations.set(new ArrayList()); cachedColorSpaces = new ThreadLocal>(); cachedColorSpaces.set(new HashMap()); fileSpecificationKeys = new ThreadLocal>(); fileSpecificationKeys.set(new HashSet()); } public static PDDocument getDocument() { return document.get(); } public static void setDocument(PDDocument document) { StaticContainers.document.set(document); } public static PDFAFlavour getFlavour() { return flavour.get(); } public static void setFlavour(PDFAFlavour flavour) { StaticContainers.flavour.set(flavour); if (roleMapHelper.get() != null) { roleMapHelper.get().setFlavour(flavour); } } public static TaggedPDFRoleMapHelper getRoleMapHelper() { return roleMapHelper.get(); } public static void setRoleMapHelper(Map roleMap) { StaticContainers.roleMapHelper.set(new TaggedPDFRoleMapHelper(roleMap, StaticContainers.flavour.get())); } }