11

General question

I have two projects A and B; B has a dependency on A. I want to generate some code in B with an Annotation Processor, based on annotations on objects in A. When I run the compilation with the correct Processor implementation, only the annotated objects from B are picked up.

I understand that scanning other JARs must be disabled by default, because you usually don't want to do an annotation scan for all your dependencies. I also understand that it may be impossible to do what I want to do because of compiler magic - which I don't know a lot about - but I'm hoping it's not.

Specific case

My projects are called DB and WEB. WEB obviously depends on DB for its JPA access; this is configured in Maven. Due to a number of architectural choices, DB must remain a separate JAR. DB doesn't use Spring except for some annotations which are consumed by WEB; WEB uses Spring MVC.

I'm trying to generate the CrudRepository interfaces for all my JPA entities with an Annotation Processor. The @Repository objects are supposed to go in a repo package in the WEB project, so they can be used with @Autowired wherever in my WEB application. The annotation I'm performing the scan for is @javax.persistence.Entity, but I've also tried a custom annotation, with the same results.

@SupportedAnnotationTypes("javax.persistence.Entity")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class RepositoryFactory extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element e : roundEnv.getElementsAnnotatedWith(Entity.class)) {
            if (e.getKind() != ElementKind.CLASS) {
                continue;
            }
            // TODO: implement logic to skip manually implemented Repos
            try {
                String name = e.getSimpleName().toString();
                TypeElement clazz = (TypeElement) e;

                JavaFileObject f = processingEnv.getFiler().
                        createSourceFile("blagae.web.repo." + name + "Repo");
                try (Writer w = f.openWriter()) {
                    PrintWriter pw = new PrintWriter(w);
                    pw.println("package blagae.web.repo;");
                    pw.println("import org.springframework.data.repository.CrudRepository;");
                    pw.printf("import %s;\n", clazz.toString());
                    pw.println("import org.springframework.stereotype.Repository;");
                    pw.println("@Repository");
                    pw.printf("public interface %sRepo extends CrudRepository<%s, Long> {}\n", name, name);
                    pw.flush();
                }
            } catch (IOException ex) {
                Logger.getLogger(RepositoryFactory.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return false;
    }
}

Ideally, I'd love for someone to tell me about an annotation that would be as simple as

@ComponentScan(basePackages = "blagae.db.*")

But of course, I'm not counting on that because it would probably be documented somewhere. As a workaround, I could just add the Spring dependency to the db and generate the classes there, but they only serve a purpose in the Spring MVC app. I'm also wary of the config it might take to make this work.

UPDATE

Some extra info: I'm using the maven-processor-plugin, which I've verified to work well in the WEB project for classes that are defined there. However, I specifically want access classes annotated in the dependency project DB. I have looked into the method AbstractProcessor::getSupportedOptions but it's unclear to me what I could do there.

Maven config:

<plugin>
    <groupId>org.bsc.maven</groupId>
    <artifactId>maven-processor-plugin</artifactId>
    <version>2.2.4</version>
    <configuration>
        <processors>
            <processor>blagae.utils.RepositoryFactory</processor>
        </processors>
    </configuration>
    <executions>
        <execution>
            <id>process</id>
            <goals>
                <goal>process</goal>
            </goals>
            <phase>generate-sources</phase>
        </execution>
    </executions>
</plugin>

SUGGESTION

Another random thought I had would be to run a JavaCompiler process for the DB project in WEB, but how would I inject my Processor ?

blagae
  • 2,342
  • 1
  • 27
  • 48

3 Answers3

8

Annotation processor works on compilation phase of your project (WEB in your case) and compiler compiles this project. Dependencies of current project are already compiled and compiler (and as result your annotation processor) don't touch (or have no access) third party libraries (DB).

You can try to extract annotation processor into separate project/jar and use it in WEB and DB projects. In this case annotation processor will create CrudRepository on compilation phase of concrete project. And all generated classes in DB project will be available in WEB.

Denys Denysiuk
  • 775
  • 5
  • 11
  • You're right that the Annotation Processor doesn't touch third party libraries. That was specifically my question: how to force access, if it's not impossible, to a dependency ... – blagae Jun 18 '15 at 13:45
  • @blagae, yes, this is impossible, because annotation processor works only with source files (`.java`, but not compiled file - `.class`). In other case java compiler should decompile already compiled code to process it - this is absurd. – Denys Denysiuk Jun 18 '15 at 14:07
  • Please see the update to my original question, specifically about using `JavaCompiler` – blagae Jun 18 '15 at 14:26
  • @blagae `javac /path/to/sources \ -cp /path/to/classpath \ -processorpath /path/to/annotation-processor.jar` – Denys Denysiuk Jun 18 '15 at 14:30
  • See: http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#options – Denys Denysiuk Jun 18 '15 at 14:32
  • I have to accept this answer, since the correct answer to my questions is, as far as I can see, "this is impossible" – blagae Jun 22 '15 at 11:42
  • This answer is helpful to me – Thomas Liao Mar 15 '21 at 07:03
5

Personally, I would extract the annotation processor in a separate maven module and add a dependency to it from the WEB module.

However, this doesn't matter that much for successfully triggering an annotation processor.

In order to have an annotation processor working, there are two things you have to provide:

Since you mentioned that currently no classes are generated, I would assume that you're missing the meta file. So, open your WEB project and navigate to src/main/resouces folder. Within, you have to create a META-INF folder with a nested services folder in it. Then, in services create a file, named javax.annotation.processing.Processor. The content of the file should list the fully-qualified class name(s) of your annotation processor(s). If there is more than one annotation processor, the fully-qualified class-names should be on separate lines. But since you have just one, you'd have something like:

com.yourdomain.processor.RepositoryFactory

Note that you will have to change this line with the actual fully-quallified class-name of your annotation processor.

In the end, you should end up with similar structure:

enter image description here

This meta file is important, because otherwise the compiler is not aware of the user-defined annotation processors. Having it, it will make use of all the registered processors.

After that, when you do a mvn clean install all your modules will be cleaned and built. However, since the compiler will now be aware of your annotation processor, it will trigger it. All the generated sources will be located (by default) in the target/generated-sources folder. Moreover, they will all be under the package you've configured in the annotation process, i.e. blagae.web.repo.

In order to use the generated sources within your code, you will have to add the targer/generated-sources to the project classpath. If you don't want to rely on the IDE to do this, you can extend the maven <build> by adding the target/generated-sources to the classpath. Something like:

<build>
    <resources>
        ...
        <resource>
            <directory>${project.build.directory}/generated-resources</directory>
        </resource>
    </resources>
</build>
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • I'm sorry to say this didn't work. The problem is still that the precompiler run only picks up annotated classes/methods in the current project (i.e. as of yet uncompiled classes). – blagae Jun 18 '15 at 13:39
  • I was already using the maven-processor-plugin and it did pick up other annotations that were used in WEB itself (e.g. @Controller) during dummy runs. Your suggestion does the same thing as the maven-processor-plugin, i.e. works great for the current project, but doesn't pick up on dependencies – blagae Jun 18 '15 at 13:41
-1

In your project A include the META-INF/beans.xml file, which will contain following:

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee <a href="http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"</a>
       version="1.1" bean-discovery-mode="all">
</beans>

and give it a try. You should use JavaEE 7/CDI 1.1. Refer to Java EE 7 Deployment Descriptors for more information.

You can also refer to this related question: How to @Inject object from different project module included as jar

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
barun
  • 393
  • 1
  • 5
  • 19