Mapstruct - cannot find symbol [Kotlin + Maven] - maven

I'm having the following error when I run the command mvn clean install:
[ERROR] /Users/xxx/xxx/xxx/xxx.xxx/target/generated-sources/kapt/compile/com/xxx/xxx/xxx/xxx/DataMapperImpl.java:[10,40] cannot find symbol
[ERROR] symbol: class DataMapperDecorator
[ERROR] /Users/xxx/xxx/xxx/xxx.xxx/target/generated-sources/kapt/compile/com/xxx/xxx/xxx/xxx/DataMapperImpl.java:[10,74] cannot find symbol
[ERROR] symbol: class DataMapper
[ERROR] /Users/xxx/xxx/xxx/xxx.xxx/xxx/generated-sources/kapt/compile/com/xxx/xxx/xxx/api/DataMapperImpl.java:[12,19] cannot find symbol
[ERROR] symbol: class DataMapper
[ERROR] location: class com.xxx.xxx.xxx.xxx.DataMapperImpl
It seems that after mapstruct has generated the DataMapperImpl.java class it is not able to find the classes DataMapper and DataMapperDecoretor.
The code related to mapstruct is in a xxx.kt file:
//Other stuff
...
#Mapper
#DecoratedWith(DataMapperDecorator::class)
interface DataMapper {
#Mappings(
Mapping(source = "data.value", target = "dataValue"),
Mapping(source = "data.current.value", target = "currentValue"),
)
fun toDto(data: Data) : RefDataDto
}
abstract class DataMapperDecorator : DataMapper {
#Autowired
private val delegate: DataMapper? = null
override fun toDto(data: Data): dataDto {
val dataDto = delegate!!.toDto(data)
dataDto.primaryValue = data.primaryValue?.let { CurrencyUtil.toMajor(it) }
return dataDto
}
}
Regarding the pom files I have in the root file:
...
<properties>
...
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
...
and this is the pom of the module where I'm using mapstruct:
...
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>src/main/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
I hid some part of the files with dots and I'm not using the project Lombok (I saw same problems related with it we you are trying to use these projects together).
UPDATE 1:
I noticed that the error is related to the fact that from the generated class DataMapperImpl.java the package where there are the classes that this generated class should use is not visible. Indeed I see this error:
[ERROR] /Users/xxx/xxx/xxx/xxx.xxx/xxx/generated-sources/kapt/compile/com/xxx/xxx/xxx/xxx/RefDataMapperImpl.java:[3,47] package com.my.package.application.domain does not exist
and of course this package exist!
UPDATE 2:
I'm continuing to investigate on this issue. I tried to make it simpler deleting the DataMapperDecorator and put the DataMapper, the Data and the DataDto class in the same file. Still the same error cannot find symbol: class for all the three classes. I'm not sure if this is related to the fact that in the DataMapperImpl (the generated class) doesn't have the import of these classes. There are imports just for the standard java libraries such as:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import javax.annotation.processing.Generated;
EDIT 1:
From the log I can also see the following warning:
[WARNING] 'tools.jar' was not found, kapt may work unreliably
UPDATE 3:
Without mapstruct under target->classes (using IntelliJ IDEA) I can see the classes of my project. On the other hand when I have introduced mapstruct what I'm seeing is that the mapstruct classes are generated under target->generated-sources->kapt->compile but under target->classes I don't see the other classes. Could the mapstruct classes be generated earlier than the other classes of my project causing the compiler to not find the Data, DataDto classes?

SOLVED!
The problem was due to the order of the compilation. By default the java compiler is executed before the kotlin compiler. That why the code generated by mapstruct wasn't able to find the kotlin classes. So it is needed to compiler the koltin classes before and then the java classes.
"The idea is to disable default compile execution and introduce our own to get control over the order in which goals are executed, so that we could run kotlin compiler before java compiler."
https://discuss.kotlinlang.org/t/maven-compiler-plugin-question/5260/4
So the solution came introducing the maven plugin:
https://kotlinlang.org/docs/maven.html#compile-kotlin-and-java-sources
So I added this to my pom file:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>

I am not sure how Kotlin works with multiple classes in one source file.
What I would suggest is that you use dedicated files for the decorator and the mapper. That way MapStruct will create the correct code.
MapStruct is a Java annotation processor, we do not know anything about the Kotlin structure. It seems like the packages returned by the java annotation processing API are not correct.

Addition to #SGiux answer. The order of plugins in pom also matters:
kotlin-maven-plugin
maven-compiler-plugin

Related

How can I compile a project that implements interfaces in generated sources folder from openapi generator?

I am using the OpenAPI generator maven plugin with kotlin-spring generator to generate interfaces for my API based on the specification.
As an example, I used the specification by this blog post and the following plugin configuration:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>5.1.0</version>
<executions>
<execution>*
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>
${project.basedir}/src/main/resources/petstore.yml
</inputSpec>
<generatorName>kotlin-spring</generatorName>
<modelNameSuffix>Dto</modelNameSuffix>
<configOptions>
<basePackage>com.example</basePackage>
<apiPackage>com.example.api</apiPackage>
<modelPackage>com.example.model</modelPackage>
<configPackage>com.example.config</configPackage>
<delegatePattern>true</delegatePattern>
<interfaceOnly>true</interfaceOnly>
<supportingFilesToGenerate>
ApiUtil.kt
</supportingFilesToGenerate>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
When I run mvn clean generate-sources then the files are properly generated in target/generated-sources/openapi/....
I then create an implementation of the delegate in my src folder where I can override the methods of the generated interface:
package com.example.api
class PetsApiDelegateImpl : PetsApiDelegate {
}
Up to now, everything is good and IntelliJ is also happy with it. However, when I run mvn clean compile the target folder is deleted and re-generated again as expected but I still receive an error:
[ERROR] Failed to execute goal org.jetbrains.kotlin:kotlin-maven-plugin:1.5.31:compile (compile) on project choreographer: Compilation failure
[ERROR] /path/to/example/src/main/kotlin/com/example/api/PetsApiDelegateImpl.kt:[3,29] Unresolved reference: PetsApiDelegate
In other words, the files are generated as part of mvn clean compile but compilation still fails because the interface is not found.
How can I successfully compile this project?
We can resolve the compilation failure by adding an execution for the compile goal to the kotlin-maven-plugin that configures the generated directory as source directory.
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.build.directory}/generated-sources/kotlin/src/main/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>

Entity mapping to DTO with DI with MapStruct

I am new in mapstruct and I am using spring as DI I follow up MapStruct documentation page regarding DI containers section 4.2 I tried to map my entity to dto as below :
#Mapper(componentModel = "spring")
public interface CustomerMapper {
#Mapping(source = "registered",target = "activeProfile")
CustomerDto customerToCustomerDto(Customer customer);
}
when i run mvn install i got this error :
java:27: error: No property named "registered" exists in source parameter(s).
#Mapping(source = "registered",target = "activeProfile")
my entity using lombok and I am sure there is registered field
please help
You don't have to to remove Lombok.
You can setup it to work before MapStruct as was described here by ahus1
https://github.com/mapstruct/mapstruct/issues/510
<!-- first de-lombok the sources to make getters/setters visible for mapstruct,
but *DON'T'* add the output directory to the sources, as we will only need it for mapstruct processing -->
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>${org.projectlombok.maven.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
<configuration>
<sourceDirectory>src/main/java</sourceDirectory>
<addOutputDirectory>false</addOutputDirectory>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- use the de-lomobok files to create the necessary mappers -->
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.2.4</version>
<configuration>
<defaultOutputDirectory>
${project.build.directory}/generated-sources/mapstruct
</defaultOutputDirectory>
<processors>
<processor>org.mapstruct.ap.MappingProcessor</processor>
</processors>
<sourceDirectory>
${project.build.directory}/delombok
</sourceDirectory>
</configuration>
<executions>
<execution>
<id>process</id>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- now take the original sources together with the created mappers to the compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<annotationProcessors>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
I've solved the problem using annotationProcessors in pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
I removed lombok from Entity and created setters /getters manually and worked well
Neither you have to remove lombok from your project nor you have to mess up the maven compiler plugin. You just have to declare the lombok dependency before you declare the mapstruck dependency in your pom. With this order maven is able to de-lombok your classes, before mapstruct referes to the getters and setters. This is possibly a feature of maven's dependency mediation.
Dependency mediation - this determines what version of a dependency
will be used when multiple versions of an artifact are encountered.
Currently, Maven 2.0 only supports using the "nearest definition"
which means that it will use the version of the closest dependency to
your project in the tree of dependencies. You can always guarantee a
version by declaring it explicitly in your project's POM. Note that if
two dependency versions are at the same depth in the dependency tree,
until Maven 2.0.8 it was not defined which one would win, but since
Maven 2.0.9 it's the order in the declaration that counts: the first
declaration wins.
https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Transitive_Dependencies
The issue is, that Lombok generated properties are not visible to JSR-269 annotation processors. It uses internal api to modify the source elements directly instead of generating new artifacts from the annotated source files Therefore any annotation processor that relies on existence of getter/setter methods will not see them in the source code when it gets executed. Javac will pass the ,,original" source code to the annotation processor(in our case Mapstruct) without any modifications done by Lombok.
Right now the most clean solution how to get both of them working side-by-side is to move the Lombok annotated types and mappers into 2 separate projects. See an official example in Mapstruct repo.

Binding a custom Maven plugin to a default phase

I have a custom Maven plugin that I'm trying to bind to the package phase by default. I've tried every combination of using the #Mojo annotation along with the #Execute annotation, but it doesn't seem to auto bind.
The only way I manage to get my plugin to work is by defining it like this:
#Mojo(name = "put")
public class SSHMojo extends AbstractMojo {
And then in my project using the plugin, defining an execution. I'd like to avoid having to add the <executions> every time I want to use my plugin.
<plugin>
<groupId>com.patrickgrimard</groupId>
<artifactId>ssh-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>put</goal>
</goals>
</execution>
</executions>
<configuration>
<serverId>devopsmtl</serverId>
<host>example.com</host>
<remoteDirectory>/srv/www</remoteDirectory>
</configuration>
</plugin>
My full plugin pom can be found at https://github.com/pgrimard/ssh-maven-plugin/blob/master/pom.xml
Hi simply use the following:
#Mojo( name = "put", defaultPhase = LifecyclePhase.PACKAGE )
Apart from that i would suggest to use a newer version of maven-plugin-api (3.0 at least)...
Use annotation attribute defaultPhase (like already mentioned by khmarbaise):
#Mojo(name = "put", defaultPhase = LifecyclePhase.PACKAGE)
public class SSHMojo extends AbstractMojo { ... }
In the pom.xml of the consuming Maven project you can leave away the reference to the phase after this:
<plugin>
<groupId>com.patrickgrimard</groupId>
<artifactId>ssh-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<!-- <phase>package</phase> --><!-- needed no longer -->
<goals>
<goal>put</goal>
</goals>
</execution>
</executions>
...

Setting properties in maven with gmaven

I am trying to overwrite the following property in maven using gmaven:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<executions>
<execution>
<id>setproperty</id>
<phase>validate</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
pom.properties['main.build.directory']=project.parent.build.directory.absolutePath.replace('\\','/');
</source>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
But I get this error:;
[ERROR] Failed to execute goal org.codehaus.gmaven:gmaven-plugin:1.5:execute (setproperty) on project my-project: startup failed, script139276
2592853.groovy: 1: expecting ''', found '<EOF>' # line 1, column 84.
[ERROR] 1 error
What is wrong with the above groovy snippet?
Value of a property which is set using gmavenplus-plugin displays correctly when accessed using the plugin. It will display correctly even if it is accessed using different instances of the same plugin.
Problem arises when value of a property which is already initialized outside the plugin is altered by the plugin and it is accessed outside the plugin. Now the value of the property is not the value as updated by plugin. The updated value is now scoped within the plugin. As a workaround to solve this issue if a property has to be updated by the plugin and is required to be accessed outside the scope of plugin: do not declare or initialize it, in case it needs to be then declare and initialize the property through the plugin.
I agree with #khmarbaise that this is a little weird to do, but if you must...I'm not sure offhand why it isn't working. That plugin isn't really maintained anymore. <shamelessPlug>I think this should work:
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<id>setproperty</id>
<phase>validate</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<scripts>
<script><![CDATA[project.properties['main.build.directory']=project.parent.build.directory.replace('\\','/')]]></script>
</scripts>
</configuration>
</execution>
</executions>
</plugin>
For more info on this mojo, check out http://groovy.github.io/GMavenPlus/execute-mojo.html.
</shamelessPlug>. However, be aware that I believe this will be scoped within the plugin.

What is the format for specifying a package in the Antlr4 maven plugin?

What is the format for specifying a package in the Antlr4 maven plugin antlr4-maven-plugin?
I feel like I should be able to do the following:
<plugin>
<groupId>com.tunnelvisionlabs</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>4.0</version>
<configuration>
<arguments>package my.package.name</arguments>
</configuration>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
but that results in the following error:
[ERROR] Failed to execute goal com.tunnelvisionlabs:antlr4-maven-plugin:4.0:antlr4 (default) on project my_project: Unable to parse configuration of mojo com.tunnelvisionlabs:antlr4-maven-plugin:4.0:antlr4 for parameter arguments: Cannot assign configuration entry 'arguments' with value 'package my.package.name' of type java.lang.String to property of type java.util.List -> [Help 1]
If I am you, I will make a maven project per package and try this
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>4.0</version>
<configuration>
<sourceDirectory>${basedir}/src</sourceDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
but usually, When I pass an argument in maven configuration, I do the following. but I am not sure of that syntax in antlr4
<plugin>
<groupId>com.tunnelvisionlabs</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>4.0</version>
<configuration>
<arguments>
<argument>-package</argument>
<argument>my.package.name</argument>
</arguments>
</configuration>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
Edit: Notice the - in front of package so the antlr-maven-plugin will recognize it as a parameter
The package is automatically determined based on the location of the file in your project, similar to the way the package is determined for Java files. The output is also placed in a location determined by the location of the source file. To change the package where the code is generated, you'll need to move the grammar file.
Other arguments can be specified like this:
<arguments>
<argument>arg1</argument>
<argument>arg2</argument>
</arguments>
Your configuration arguments syntax is wrong.
Please change the configuration of antlr4-maven-plugin from
<configuration>
<arguments>package my.package.name</arguments>
</configuration>
to:
<configuration>
<arguments>
<argument>-package</argument>
<argument>my.package.name</argument>
</arguments>
</configuration>
In order to add package information to the generated code you must add the following annotation to the g4 file:
#header {
package com.this.is.my.package;
}
I tried
#header {
package com.this.is.my.package;
}
but when you have imports it adds package line for each file imported and as a result compiler errors raised in generated file. You have to be careful to add #header so file with imported grammars had only one package line. I think it's a bug.
I have the Demo.g4 inside src/main/antlr4/de/schmitz.
Now the classes are generated to target/generated-sources/antlr4/de/schmitz.
The package is de.schmitz.
Everything is correct.
Now I want to change the package and folders of the generated classes (actually NOT moving my Demo.g4):
<arguments>
<argument>-package</argument>
<argument>my.package</argument>
</arguments>
The classes are generated to target/generated-sources/antlr4/de/schmitz.
The package is my.package.
This cannot be compiled, so it could be an error.
So I want to change the output directory:
<outputDirectory>${project.build.directory}/generated-sources/antlr4/my/package</outputDirectory>
Now it get's buggy.
The package is my.package.
But the folder is target/generated-sources/antlr4/my/package/de/schmitz.
So it's concatenated instead of overwritten.

Resources