AttachmentsClassLoader fails when calling setURLStreamHandlerFactory (#4512)

This commit is contained in:
Manos Batsis 2019-01-08 15:23:47 +02:00 committed by Joel Dudley
parent 8e61d11a49
commit 2acb3d37cb

View File

@ -19,6 +19,8 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.*
import java.lang.reflect.AccessibleObject.setAccessible
import java.net.URLStreamHandlerFactory
/**
* A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only
@ -41,10 +43,11 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
private val log = contextLogger()
init {
// This is required to register the AttachmentURLStreamHandlerFactory.
URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory)
// Apply our own URLStreamHandlerFactory to resolve attachments
setOrDecorateURLStreamHandlerFactory()
}
// Jolokia and Json-simple are dependencies that were bundled by mistake within contract jars.
// In the AttachmentsClassLoader we just ignore any class in those 2 packages.
private val ignoreDirectories = listOf("org/jolokia/", "org/json/simple/")
@ -115,6 +118,50 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
return it.toByteArray()
}
}
/**
* Apply our custom factory either directly, if `URL.setURLStreamHandlerFactory` has not been called yet,
* or use a decorator and reflection to bypass the single-call-per-JVM restriction otherwise.
*/
private fun setOrDecorateURLStreamHandlerFactory() {
// Retrieve the `URL.factory` field
val factoryField = URL::class.java.getDeclaredField("factory")
// Make it accessible
factoryField.isAccessible = true
// Check for preset factory, set directly if missing
val existingFactory: URLStreamHandlerFactory? = factoryField.get(null) as URLStreamHandlerFactory?
if (existingFactory == null) {
URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory)
}
// Otherwise, decorate the existing and replace via reflection
// as calling `URL.setURLStreamHandlerFactory` again will throw an error
else {
log.warn("The URLStreamHandlerFactory was already set in the JVM. Please be aware that this is not recommended.")
// Retrieve the field "streamHandlerLock" of the class URL that
// is the lock used to synchronize access to the protocol handlers
val lockField = URL::class.java.getDeclaredField("streamHandlerLock")
// It is a private field so we need to make it accessible
// Note: this will only work as-is in JDK8.
lockField.isAccessible = true
// Use the same lock to reset the factory
synchronized(lockField.get(null)) {
// Reset the value to prevent Error due to a factory already defined
factoryField.set(null, null)
// Set our custom factory and wrap the current one into it
URL.setURLStreamHandlerFactory(
// Set the factory to a decorator
object : URLStreamHandlerFactory {
// route between our own and the pre-existing factory
override fun createURLStreamHandler(protocol: String): URLStreamHandler? {
return AttachmentURLStreamHandlerFactory.createURLStreamHandler(protocol)
?: existingFactory.createURLStreamHandler(protocol)
}
}
)
}
}
}
}
/**
@ -200,3 +247,4 @@ object AttachmentURLStreamHandlerFactory : URLStreamHandlerFactory {
}
}
}