This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openjpa.git
The following commit(s) were added to refs/heads/master by this push: new beb1255 extracting directory enhancement logic in a runnable for junit5 module to be able to reuse it easily beb1255 is described below commit beb125500f10b77cca8d40b88d23252af78fae21 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Tue Mar 31 09:28:32 2020 +0200 extracting directory enhancement logic in a runnable for junit5 module to be able to reuse it easily --- ...ension.java => OpenJPADirectoriesEnhancer.java} | 98 ++++--- .../openjpa/junit5/internal/OpenJPAExtension.java | 318 +-------------------- 2 files changed, 56 insertions(+), 360 deletions(-) diff --git a/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPAExtension.java b/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPADirectoriesEnhancer.java similarity index 84% copy from openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPAExtension.java copy to openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPADirectoriesEnhancer.java index 2478cad..7c62942 100644 --- a/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPAExtension.java +++ b/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPADirectoriesEnhancer.java @@ -22,7 +22,6 @@ import org.apache.openjpa.conf.OpenJPAConfigurationImpl; import org.apache.openjpa.enhance.AsmAdaptor; import org.apache.openjpa.enhance.PCEnhancer; import org.apache.openjpa.enhance.PersistenceCapable; -import org.apache.openjpa.junit5.OpenJPASupport; import org.apache.openjpa.lib.log.LogFactory; import org.apache.openjpa.lib.log.LogFactoryImpl; import org.apache.openjpa.lib.log.SLF4JLogFactory; @@ -33,9 +32,6 @@ import org.apache.xbean.asm7.ClassReader; import org.apache.xbean.asm7.Type; import org.apache.xbean.asm7.shade.commons.EmptyVisitor; import org.apache.xbean.finder.ClassLoaders; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.util.AnnotationUtils; import serp.bytecode.BCClass; import serp.bytecode.Project; @@ -47,7 +43,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -66,53 +61,62 @@ import static org.apache.xbean.asm7.ClassReader.SKIP_CODE; import static org.apache.xbean.asm7.ClassReader.SKIP_DEBUG; import static org.apache.xbean.asm7.ClassReader.SKIP_FRAMES; -public class OpenJPAExtension implements BeforeAllCallback { - private static final Logger LOGGER = Logger.getLogger(OpenJPAExtension.class.getName()); +public class OpenJPADirectoriesEnhancer implements Runnable { + private static final Logger LOGGER = Logger.getLogger(OpenJPADirectoriesEnhancer.class.getName()); + public static final StackTraceElement[] NO_STACK_TRACE = new StackTraceElement[0]; + + private final boolean auto; + private final String[] entities; + private final Class<?> logFactory; + + public OpenJPADirectoriesEnhancer(final boolean auto, final String[] entities, final Class<?> logFactory) { + this.auto = auto; + this.entities = entities; + this.logFactory = logFactory; + } @Override - public void beforeAll(final ExtensionContext context) { - AnnotationUtils.findAnnotation(context.getElement(), OpenJPASupport.class).ifPresent(s -> { - final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - final OpenJpaClassLoader enhancementClassLoader = new OpenJpaClassLoader( - classLoader, createLogFactory(classLoader, s.logFactory())); - final Thread thread = Thread.currentThread(); - thread.setContextClassLoader(enhancementClassLoader); - try { - if (s.auto()) { + public void run() { + final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + final OpenJpaClassLoader enhancementClassLoader = new OpenJpaClassLoader( + classLoader, createLogFactory(classLoader)); + final Thread thread = Thread.currentThread(); + thread.setContextClassLoader(enhancementClassLoader); + try { + if (auto) { + try { + ClassLoaders.findUrls(enhancementClassLoader.getParent()).stream() + .map(org.apache.xbean.finder.util.Files::toFile) + .filter(File::isDirectory) + .map(File::toPath) + .forEach(dir -> { + LOGGER.fine(() -> "Enhancing folder '" + dir + "'"); + try { + enhanceDirectory(enhancementClassLoader, dir); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + }); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + } else { + Stream.of(entities).forEach(e -> { try { - ClassLoaders.findUrls(enhancementClassLoader.getParent()).stream() - .map(org.apache.xbean.finder.util.Files::toFile) - .filter(File::isDirectory) - .map(File::toPath) - .forEach(dir -> { - LOGGER.fine(() -> "Enhancing folder '" + dir + "'"); - try { - enhanceDirectory(enhancementClassLoader, dir); - } catch (final IOException e) { - throw new IllegalStateException(e); - } - }); - } catch (final IOException e) { - throw new IllegalStateException(e); + enhancementClassLoader.loadClass(e); + } catch (final ClassNotFoundException e1) { + throw new IllegalArgumentException(e1); } - } else { - Stream.of(s.entities()).forEach(e -> { - try { - enhancementClassLoader.loadClass(e); - } catch (final ClassNotFoundException e1) { - throw new IllegalArgumentException(e1); - } - }); - } - } finally { - thread.setContextClassLoader(enhancementClassLoader.getParent()); + }); } - }); + } finally { + thread.setContextClassLoader(enhancementClassLoader.getParent()); + } } - private LogFactory createLogFactory(final ClassLoader classLoader, final Class<?> logFactory) { + private LogFactory createLogFactory(final ClassLoader classLoader) { try { - if (logFactory == LogFactory.class) { + if (logFactory == null || logFactory == LogFactory.class) { try { return new SLF4JLogFactory(); } catch (final Error | Exception e) { @@ -343,8 +347,14 @@ public class OpenJPAExtension implements BeforeAllCallback { } private static class MissingEnhancement extends RuntimeException { + private MissingEnhancement() { + setStackTrace(NO_STACK_TRACE); + } } private static class AlreadyEnhanced extends RuntimeException { + private AlreadyEnhanced() { + setStackTrace(NO_STACK_TRACE); + } } } diff --git a/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPAExtension.java b/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPAExtension.java index 2478cad..2cacd5e 100644 --- a/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPAExtension.java +++ b/openjpa-junit5/src/main/java/org/apache/openjpa/junit5/internal/OpenJPAExtension.java @@ -18,333 +18,19 @@ */ package org.apache.openjpa.junit5.internal; -import org.apache.openjpa.conf.OpenJPAConfigurationImpl; -import org.apache.openjpa.enhance.AsmAdaptor; -import org.apache.openjpa.enhance.PCEnhancer; -import org.apache.openjpa.enhance.PersistenceCapable; import org.apache.openjpa.junit5.OpenJPASupport; -import org.apache.openjpa.lib.log.LogFactory; -import org.apache.openjpa.lib.log.LogFactoryImpl; -import org.apache.openjpa.lib.log.SLF4JLogFactory; -import org.apache.openjpa.meta.MetaDataRepository; -import org.apache.openjpa.persistence.PersistenceMetaDataFactory; -import org.apache.xbean.asm7.AnnotationVisitor; -import org.apache.xbean.asm7.ClassReader; -import org.apache.xbean.asm7.Type; -import org.apache.xbean.asm7.shade.commons.EmptyVisitor; -import org.apache.xbean.finder.ClassLoaders; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.util.AnnotationUtils; -import serp.bytecode.BCClass; -import serp.bytecode.Project; -import javax.persistence.Embeddable; -import javax.persistence.Entity; -import javax.persistence.MappedSuperclass; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Collection; import java.util.logging.Logger; -import java.util.stream.Stream; - -import static java.util.Arrays.asList; -import static org.apache.xbean.asm7.ClassReader.SKIP_CODE; -import static org.apache.xbean.asm7.ClassReader.SKIP_DEBUG; -import static org.apache.xbean.asm7.ClassReader.SKIP_FRAMES; public class OpenJPAExtension implements BeforeAllCallback { private static final Logger LOGGER = Logger.getLogger(OpenJPAExtension.class.getName()); @Override public void beforeAll(final ExtensionContext context) { - AnnotationUtils.findAnnotation(context.getElement(), OpenJPASupport.class).ifPresent(s -> { - final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - final OpenJpaClassLoader enhancementClassLoader = new OpenJpaClassLoader( - classLoader, createLogFactory(classLoader, s.logFactory())); - final Thread thread = Thread.currentThread(); - thread.setContextClassLoader(enhancementClassLoader); - try { - if (s.auto()) { - try { - ClassLoaders.findUrls(enhancementClassLoader.getParent()).stream() - .map(org.apache.xbean.finder.util.Files::toFile) - .filter(File::isDirectory) - .map(File::toPath) - .forEach(dir -> { - LOGGER.fine(() -> "Enhancing folder '" + dir + "'"); - try { - enhanceDirectory(enhancementClassLoader, dir); - } catch (final IOException e) { - throw new IllegalStateException(e); - } - }); - } catch (final IOException e) { - throw new IllegalStateException(e); - } - } else { - Stream.of(s.entities()).forEach(e -> { - try { - enhancementClassLoader.loadClass(e); - } catch (final ClassNotFoundException e1) { - throw new IllegalArgumentException(e1); - } - }); - } - } finally { - thread.setContextClassLoader(enhancementClassLoader.getParent()); - } - }); - } - - private LogFactory createLogFactory(final ClassLoader classLoader, final Class<?> logFactory) { - try { - if (logFactory == LogFactory.class) { - try { - return new SLF4JLogFactory(); - } catch (final Error | Exception e) { - return new LogFactoryImpl(); - } - } - return logFactory.asSubclass(LogFactory.class).getConstructor().newInstance(); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new IllegalStateException(e); - } - } - - private void enhanceDirectory(final OpenJpaClassLoader enhancementClassLoader, final Path dir) throws IOException { - Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - if (file.getFileName().toString().endsWith(".class")) { - final String relativeName = dir.relativize(file).toString(); - try { - enhancementClassLoader.handleEnhancement( - relativeName.substring(0, relativeName.length() - ".class".length())); - } catch (final ClassNotFoundException e) { - throw new IllegalStateException(e); - } - } - return super.visitFile(file, attrs); - } - }); - } - - private static abstract class BaseClassLoader extends ClassLoader { - private BaseClassLoader(final ClassLoader parent) { - super(parent); - } - - protected abstract Class<?> doLoadClass(String name, boolean resolve) throws ClassNotFoundException; - - @Override - protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { - if (name != null && !name.startsWith("java") && !name.startsWith("sun") && !name.startsWith("jdk")) { - return doLoadClass(name, resolve); - } - return defaultLoadClass(name, resolve); - } - - protected Class<?> defaultLoadClass(final String name, final boolean resolve) throws ClassNotFoundException { - return super.loadClass(name, resolve); - } - - protected byte[] loadBytes(final String name) { - final URL url = findUrl(name); - if (url == null || "jar".equals(url.getProtocol()) /*assume done in build*/) { - return null; - } - byte[] buffer = new byte[4096]; - final ByteArrayOutputStream inMem = new ByteArrayOutputStream(buffer.length); - try (final InputStream is = url.openStream()) { - int read; - while ((read = is.read(buffer)) >= 0) { - if (read > 0) { - inMem.write(buffer, 0, read); - } - } - } catch (final IOException e) { - throw new IllegalStateException(e); - } - return inMem.toByteArray(); - } - - protected URL findUrl(final String name) { - return getResource(name.replace('.', '/') + ".class"); - } - } - - private static class OpenJpaClassLoader extends BaseClassLoader { - private static final String PERSITENCE_CAPABLE = Type.getDescriptor(PersistenceCapable.class); - private static final String ENTITY = Type.getDescriptor(Entity.class); - private static final String EMBEDDABLE = Type.getDescriptor(Embeddable.class); - private static final String MAPPED_SUPERCLASS = Type.getDescriptor(MappedSuperclass.class); - - private final MetaDataRepository repos; - private final ClassLoader tmpLoader; - private final Collection<String> alreadyEnhanced = new ArrayList<>(); - - private OpenJpaClassLoader(final ClassLoader parent, final LogFactory logFactory) { - super(parent); - - final OpenJPAConfigurationImpl conf = new OpenJPAConfigurationImpl(); - conf.setLogFactory(logFactory); - - tmpLoader = new CompanionLoader(parent); - repos = new MetaDataRepository(); - repos.setConfiguration(conf); - repos.setMetaDataFactory(new PersistenceMetaDataFactory()); - } - - @Override - protected synchronized Class<?> doLoadClass(final String name, final boolean resolve) throws ClassNotFoundException { - final Class<?> clazz = findLoadedClass(name); - if (clazz != null) { - if (resolve) { - resolveClass(clazz); - } - return clazz; - } - handleEnhancement(name); - return defaultLoadClass(name, resolve); - } - - private void handleEnhancement(final String name) throws ClassNotFoundException { - final byte[] enhanced = ensureEnhancedIfNeeded(name); - if (enhanced != null && alreadyEnhanced.add(name)) { - // we could do that but test classes will be loaded with parent loader - // so just rewrite the class on the fly assuming it was not yet read - try { - Files.write(findTarget(name), enhanced, StandardOpenOption.TRUNCATE_EXISTING); - LOGGER.info(() -> "Enhanced '" + name + "'"); - } catch (final IOException e) { - throw new ClassNotFoundException(e.getMessage(), e); - } - } - } - - private Path findTarget(final String name) { - final URL url = findUrl(name); - if (!"file".equals(url.getProtocol())) { - throw new IllegalStateException("Only file urls are supported today: " + url); - } - return Paths.get(url.getPath()); - } - - private byte[] enhance(final byte[] classBytes) { - final Thread thread = Thread.currentThread(); - final ClassLoader old = thread.getContextClassLoader(); - thread.setContextClassLoader(tmpLoader); - try (final InputStream stream = new ByteArrayInputStream(classBytes)) { - final PCEnhancer enhancer = new PCEnhancer( - repos.getConfiguration(), - new Project().loadClass(stream, tmpLoader), - repos, tmpLoader); - if (enhancer.run() == PCEnhancer.ENHANCE_NONE) { - return null; - } - final BCClass pcb = enhancer.getPCBytecode(); - return AsmAdaptor.toByteArray(pcb, pcb.toByteArray()); - } catch (final IOException e) { - throw new IllegalStateException(e); - } finally { - thread.setContextClassLoader(old); - } - } - - private boolean isJpaButNotEnhanced(final byte[] classBytes) { - try (final InputStream stream = new ByteArrayInputStream(classBytes)) { - final ClassReader reader = new ClassReader(stream); - reader.accept(new EmptyVisitor() { - @Override - public void visit(final int version, final int access, final String name, - final String signature, final String superName, final String[] interfaces) { - if (interfaces != null && asList(interfaces).contains(PERSITENCE_CAPABLE)) { - throw new AlreadyEnhanced(); // exit - } - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { - if (ENTITY.equals(descriptor) || - EMBEDDABLE.equals(descriptor) || - MAPPED_SUPERCLASS.equals(descriptor)) { - throw new MissingEnhancement(); // we already went into visit() so we miss the enhancement - } - return new EmptyVisitor().visitAnnotation(descriptor, visible); - } - }, SKIP_DEBUG + SKIP_CODE + SKIP_FRAMES); - return false; - } catch (final IOException e) { - throw new IllegalStateException(e); - } catch (final AlreadyEnhanced alreadyEnhanced) { - return false; - } catch (final MissingEnhancement alreadyEnhanced) { - return true; - } - } - - private byte[] ensureEnhancedIfNeeded(final String name) { - final byte[] classBytes = loadBytes(name); - if (classBytes == null) { - return null; - } - if (isJpaButNotEnhanced(classBytes)) { - final byte[] enhanced = enhance(classBytes); - if (enhanced != null) { - return enhanced; - } - LOGGER.info("'" + name + "' already enhanced"); - } - return null; - } - } - - private static class CompanionLoader extends BaseClassLoader { - private CompanionLoader(final ClassLoader parent) { - super(parent); - } - - @Override - protected Class<?> doLoadClass(final String name, final boolean resolve) throws ClassNotFoundException { - final Class<?> clazz = findLoadedClass(name); - if (clazz != null) { - if (resolve) { - resolveClass(clazz); - } - return clazz; - } - final byte[] content = loadBytes(name); - if (content != null) { - final Class<?> value = super.defineClass(name, content, 0, content.length); - if (resolve) { - resolveClass(value); - } - return value; - } - return defaultLoadClass(name, resolve); - } - } - - private static class MissingEnhancement extends RuntimeException { - } - - private static class AlreadyEnhanced extends RuntimeException { + AnnotationUtils.findAnnotation(context.getElement(), OpenJPASupport.class) + .ifPresent(s -> new OpenJPADirectoriesEnhancer(s.auto(), s.entities(), s.logFactory()).run()); } }