Skip to content

Commit

Permalink
smallrye#316: power annotations scanner and tck (and utils)
Browse files Browse the repository at this point in the history
Signed-off-by: Rüdiger zu Dohna <[email protected]>
  • Loading branch information
t1 committed Dec 8, 2020
1 parent 687c207 commit a4eaf7d
Show file tree
Hide file tree
Showing 49 changed files with 3,266 additions and 10 deletions.
10 changes: 8 additions & 2 deletions power-annotations/README.adoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
= Power-Annotations

Power-Annotations is actually independent of GraphQL. It's a generic mechanism for meta annotations. It consists of annotations you can put on your code and (for now only) implementation using a maven plugin to build a Jandex index file.
Power-Annotations is actually independent of GraphQL. It's a generic mechanism for meta annotations. It consists of annotations you can put on your code (these are meant to become a separate standard) and the following implementations:

1. A maven plugin to build a Jandex index file (and resolve all power annotations). This replaces the `jandex-maven-plugin`. It currently has fewer options, but as we only want to build what people actually need, give us a call if you need more!
2. A classpath scanner to build a Jandex index at runtime (and resolve all power annotations).
There's also a TCK to test, if an implementation adheres to the standard defined by the annotations. It uses an Utils API, so the TCK can also verify implementations not using Jandex. This Annotation Utils API may be convenient for your usage as well, as, e.g., it also resolves repeated annotations.

== Annotations

Expand Down Expand Up @@ -39,7 +45,7 @@ This is exactly what https://jakarta.ee/specifications/cdi/2.0/cdi-spec-2.0.html

=== Mixins

Say you have a class you want to add annotations to, but you can't; e.g., because it's a class from some library or even from the JDK. You can create your own class (or interface) and annotate it as `@MixinFor` the target class. The annotations you put on your mixin class will work as if they were on the target class (if your framework supports Power Annotations).
Say you have a class you want to add annotations to, but you can't; e.g., because it's a class from some library or even from the JDK. You can create your own class (or interface or even annotation) and annotate it as `@MixinFor` the target class. The annotations you put on your mixin class will work as if they were on the target class (if your framework supports Power Annotations).

This also works for annotations: say we're developing an application packed with annotations from JPA, which doesn't support mixins (yet). The application also uses a library that supports mixins but doesn't know about JPA, e.g. a future MP GraphQL. We want all JPA `@Id` annotations to be recognized as synonyms for GraphQL `@Id` annotations. We could create a simple mixin for the JPA annotation:

Expand Down
2 changes: 2 additions & 0 deletions power-annotations/annotations/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
</parent>

<artifactId>power-annotations</artifactId>
<name>Power Annotations Annotations</name>
<description>The Annotations resolved by Power Annotations</description>
</project>
4 changes: 3 additions & 1 deletion power-annotations/common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
<version>1.0.20-SNAPSHOT</version>
</parent>

<artifactId>power-annotations-common</artifactId>
<artifactId>power-annotations-jandex-common</artifactId>
<name>Power Annotations Jandex Common</name>
<description>Common Jandex specific Power Annotations code used by build plugins and the runtime scanner.</description>

<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,34 @@ public Set<DotName> allAnnotationNames() {
}

public void copyClassAnnotation(AnnotationInstance original, DotName className) {
ClassInfo classInfo = classes.get(className);
ClassInfo classInfo = getClassInfo(className);
AnnotationInstance copy = copyAnnotationInstance(original, classInfo);
add(copy, annotations(classInfo));
}

public void copyFieldAnnotation(AnnotationInstance original, DotName className, String fieldName) {
ClassInfo classInfo = classes.get(className);
ClassInfo classInfo = getClassInfo(className);
FieldInfo field = classInfo.field(fieldName);
AnnotationInstance annotationInstance = copyAnnotationInstance(original, field);
JandexBackdoor.add(annotationInstance, field);
add(annotationInstance, annotations(classInfo));
}

public void copyMethodAnnotation(AnnotationInstance original, DotName className, String methodName, Type... parameters) {
ClassInfo classInfo = classes.get(className);
ClassInfo classInfo = getClassInfo(className);
MethodInfo method = classInfo.method(methodName, parameters);
AnnotationInstance annotationInstance = copyAnnotationInstance(original, method);
JandexBackdoor.add(annotationInstance, method);
add(annotationInstance, annotations(classInfo));
}

private ClassInfo getClassInfo(DotName className) {
ClassInfo classInfo = classes.get(className);
if (classInfo == null)
throw new RuntimeException("class not in index: " + className);
return classInfo;
}

private AnnotationInstance copyAnnotationInstance(AnnotationInstance original, AnnotationTarget annotationTarget) {
return createAnnotationInstance(original.name(), annotationTarget, original.values().toArray(new AnnotationValue[0]));
}
Expand Down
4 changes: 3 additions & 1 deletion power-annotations/maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

<artifactId>power-jandex-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<name>Power Annotations Jandex Maven Plugin</name>
<description>Replaces the `jandex-maven-plugin` to generate a Jandex index file, but with the Power Annotations resolved.</description>

<prerequisites>
<maven>3.5</maven>
Expand Down Expand Up @@ -45,7 +47,7 @@

<dependency>
<groupId>io.smallrye</groupId>
<artifactId>power-annotations-common</artifactId>
<artifactId>power-annotations-jandex-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
Expand Down
33 changes: 30 additions & 3 deletions power-annotations/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,42 @@
<artifactId>smallrye-graphql-parent</artifactId>
<version>1.0.20-SNAPSHOT</version>
</parent>

<artifactId>smallrye-power-annotations-parent</artifactId>
<packaging>pom</packaging>

<name>Power Annotations</name>
<name>Power Annotations Parent POM</name>
<description>Power-Annotations is a generic mechanism for meta annotations, most notably stereotypes and mixins.</description>

<modules>
<module>annotations</module>
<module>utils</module>
<module>tck</module>
<module>common</module>
<module>maven-plugin</module>
<module>scanner</module>
</modules>

<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>*Behavior</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>
43 changes: 43 additions & 0 deletions power-annotations/scanner/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-power-annotations-parent</artifactId>
<version>1.0.18-SNAPSHOT</version>
</parent>

<artifactId>power-annotations-scanner</artifactId>
<name>Power Annotations Jandex Runtime Scanner</name>
<description>Build a Jandex index at runtime from the classpath and resolve power annotations</description>

<dependencies>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>
<version>2.2.2.Final</version>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>power-annotations-jandex-common</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.18.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.github.t1.powerannotations.scanner;

import static java.util.logging.Level.FINE;
import static java.util.stream.Collectors.joining;

import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.jandex.IndexReader;
import org.jboss.jandex.IndexView;

public class IndexBuilder {
public static IndexView loadOrScan() {
IndexView jandex = load().orElseGet(Scanner::scan);
if (LOG.isLoggable(LEVEL)) {
LOG.log(LEVEL, "------------------------------------------------------------");
jandex.getKnownClasses()
.forEach(classInfo -> LOG.log(LEVEL, classInfo.name() + " :: " + classInfo.classAnnotations().stream()
.filter(instance -> !instance.name().toString().equals("kotlin.Metadata")) // contains binary
.map(Object::toString).collect(joining(", "))));
LOG.log(LEVEL, "------------------------------------------------------------");
}
return jandex;
}

@SuppressWarnings("UnusedReturnValue")
private static Optional<IndexView> load() {
try (InputStream inputStream = getClassLoader().getResourceAsStream("META-INF/jandex.idx")) {
return Optional.ofNullable(inputStream).map(IndexBuilder::load);
} catch (RuntimeException | IOException e) {
throw new RuntimeException("can't read index file", e);
}
}

private static IndexView load(InputStream inputStream) {
try {
return new IndexReader(inputStream).read();
} catch (RuntimeException | IOException e) {
throw new RuntimeException("can't read Jandex input stream", e);
}
}

private static ClassLoader getClassLoader() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return (classLoader == null) ? ClassLoader.getSystemClassLoader() : classLoader;
}

private static final Logger LOG = Logger.getLogger(IndexBuilder.class.getName());
private static final Level LEVEL = FINE;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.github.t1.powerannotations.scanner;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

class IndexerConfig {
private final List<String> exclude = new ArrayList<>();

IndexerConfig() {
this("META-INF/jandex.properties");
}

IndexerConfig(String resource) {
try (InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resource)) {
if (inputStream == null)
return;
Properties properties = new Properties();
properties.load(inputStream);
loadExcludeConfig(properties);
} catch (IOException e) {
throw new RuntimeException("can't load " + resource, e);
}
}

private void loadExcludeConfig(Properties properties) {
String excludeString = properties.getProperty("exclude", null);
if (excludeString != null) {
try {
Stream.of(excludeString.split("\\s+"))
.map(this::gavToRegex)
.forEach(this.exclude::add);
} catch (Exception e) {
throw new RuntimeException("can't parse exclude config", e);
}
}
}

private String gavToRegex(String groupArtifact) {
Matcher matcher = Pattern.compile("(?<group>[^:]+):(?<artifact>[^:]+)").matcher(groupArtifact);
if (!matcher.matches())
throw new RuntimeException("expect `group:artifact` but found `" + groupArtifact + "`");
return ".*/" + matcher.group("group") + "/.*/" + matcher.group("artifact") + "-.*\\.jar";
}

public Stream<String> excludes() {
return exclude.stream();
}
}
Loading

0 comments on commit a4eaf7d

Please sign in to comment.