I have 2 xsd schemas from which Im generating a class Message using maven-jaxb2-plugin. The main xsd is message.xsd and it imports common.xsd which holds some common types. However both have different target namespaces.
This results in jaxb xml unmarshaller not reading the fields in the xml and using the default values. If in the generated class I manually set the namespace to "message" it works. Is there a way to place both generated class fields under the same namespace? Or perhaps is my XML message wrong? Or am I missing a jaxb2 generator parameter somewhere? Should I use a jaxb binding to solve this?
I cannot change the xsd namespace definitions. A lot of answers are so very close to what I need but none are the actual issue I have.
This generates a Message class that looks sort of like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"computer"
})
#XmlRootElement(name = "message", namespace = "message")
public class Message implements ToString2
{
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"date",
"info"
})
public static class Computer implements ToString2
{
#XmlElement(namespace = "message", required = true)
protected int date;
#XmlElement(name = "info", namespace = "message", required = true)
protected InfoType info;
}
}
The Info class is generated from the common.xsd and it looks like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "InfoType", propOrder = {
"number",
"id"
})
public class InfoType implements ToString2
{
#XmlElement(namespace = "")
#XmlSchemaType(name = "integer")
protected int number;
#XmlElement(namespace = "")
#XmlSchemaType(name = "integer")
protected int id;
}
I send this xml:
<?xml version="1.0" encoding="utf-8"?>
<message xmlns="message">
<computer>
<info>
<name>2</name>
<id>999998</id>
</info>
<date>20220508</date>
</computer>
</message>
package-info.java has the namespace set to "common" which is why I assume the namespace on the fields in Info are "" it looks like this:
#javax.xml.bind.annotation.XmlSchema(namespace = "common", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.myapp.message
pom:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.14.0</version>
<dependencies>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
</dependencies>
<configuration>
<accessExternalSchema>all</accessExternalSchema>
<specVersion>2.1</specVersion>
<cleanPackageDirectories>false</cleanPackageDirectories>
<extension>true</extension>
<args>
<arg>-Xfluent-api</arg>
<arg>-XtoString</arg>
</args>
<plugins>
<plugin>
<groupId>net.java.dev.jaxb2-commons</groupId>
<artifactId>jaxb-fluent-api</artifactId>
<version>${maven-jaxb2-plugin.fluent-api.version}</version>
</plugin>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>${jaxb2-basics-runtime.version}</version>
</plugin>
</plugins>
</configuration>
<executions>
<execution>
<id>generate-message</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemaDirectory>src/main/resources/message</schemaDirectory>
<schemaIncludes>
<include>message.xsd</include>
</schemaIncludes>
<bindingDirectory>src/main/resources/jaxb</bindingDirectory>
<generateDirectory>target/generated-sources/jaxb/message</generateDirectory>
<generatePackage>com.myapp.message
</generatePackage>
</configuration>
</execution>
</executions>
</plugin>
From other xsd generations I assume changing targetNamespace to the same namespace and using include instead of import would do the trick, however that is not an option.
Manually changing namespace in the #XmlElement(namespace = "") solves the problem, however that doesn't do much for me as it will get written over on the next generation.
I tried looking into xjb. However Im unsure if that is actually the way, if I set it to bind to the top element it has trouble resolving types from the commons.xsd - might be worth pursing?
Related
I am trying to get a springboot (2.6.2) project to work with both AspectJ and Spring AOP.
I have the following sample classes:
#Entity
public class Item {
#Id #Getter private String uuid = UUID.randomUUID().toString();
private String name;
#Verify.Access
public String getName() {
return name;
}
}
public #interface Verify {
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#interface Access {}
}
#Aspect
#Slf4j
public class MyAspect {
#Before("#annotation(Verify.Access)")
public void beforeAnnotation(JoinPoint joinPoint) {
log.error("BEFORE ANNOTATION");
}
}
#Aspect
#Service
public class OtherAspect {
#Autowired private MyUtility myUtility;
#Around("#annotation(SystemCall)")
public Object run(#NonNull final ProceedingJoinPoint join) throws Throwable {
return myUtility.getInfo();
}
}
#Service
#Data
public class MyUtility {
Object info;
}
My pom.xml file has the following plugins defined:
<plugin>
<groupId>com.nickwongdev</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.12.6</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<proc>none</proc>
<complianceLevel>${java.version}</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
<forceAjcCompile>true</forceAjcCompile>
<sources/>
<weaveDirectories>
<weaveDirectory>${project.build.directory}/classes</weaveDirectory>
</weaveDirectories>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
I have also defined a src/main/resources/org/aspectj/aop.xml:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<include within="mypackage..*" />
<include within="org.springframework.boot..*" />
</weaver>
<aspects>
<aspect name="mypackage.MyAspect" />
</aspects>
</aspectj>
It seems to compile okay and I see the info messages that the join points are being advised.
However, in the OtherAspect the autowired MyUtility is not getting set.
From what I could find I would expect Spring to recognize OtherAspect as a Component and Autowire in MyUtility but instead I get a NullPointerException.
Any thoughts?
Thanks!
OK, I had a little bit of time and prepared the MCVE which actually would have been your job to provide. I made the following assumptions:
You need native AspectJ, because you want to weave a target class which is not a Spring bean.
You want to use compile-time, not load-time weaaving. Therefore, you would use AspectJ Maven Plugin.
You want to use Spring dependency injection for wiring Spring beans into native AspectJ aspects, as described in the Spring manual, i.e. using an aspectOf factory method for the native aspect in Spring.
You absolutely insist on combining Lombok and native AspectJ, even though they are incompatible out of the box. I.e., you need a workaround in Maven, either binary weaving (e.g. if Lombok is only used for your non-aspect classes) or a "delombok" build step (e.g. if your aspects also use Lombok, which unfortunately they do, using the #Slf4j Lombok annotation in MyAspect.
What I changed in your setup:
I removed the dependency on Spring Data JPA to make things easier, because I was too lazy to set up a dummy in-memory database. It is not relevant for the solution here. I.e., I also commented out the #Entity and #Id annotations in class Item.
You already configured a "delombok" build step, which I wanted to stick with, because it seems to be your preference. Hence, your sample code only compiles with AspectJ Maven when using ${project.build.directory}/generated-sources/delombok as the source directory. Your idea to use a <weaveDirectory> does not work, because the aspect with the Lombok annotation does not compile that way, as it refers to the Lombok-generated static log field.
I removed the #Service annotation from the native AspectJ aspect, because that would lead to problems when wiring the application. Instead, I added a #Bean factory method to OtherAspect, so we can use #Autowired MyUtility myUtility there. In the same aspect, I also switched from #annotation(SystemCall) (due to missing code in your example) to #annotation(Verify.Access) in order to have something to test against.
I removed the superfluous aop.xml file.
I added a little Spring Boot driver application.
I switched from the no longer maintained com.nickwongdev AspectJ Maven plugin to the current dev.aspectj plugin which has more features and supports Java 17+, too.
The whole application looks like this:
<?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>
<groupId>org.example</groupId>
<artifactId>SO_AJ_SpringAutowireBeanNativeAspect_74661663</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<aspectj.version>1.9.9.1</aspectj.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>dev.aspectj</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.13.1</version>
<configuration>
<complianceLevel>${maven.compiler.target}</complianceLevel>
<proc>none</proc>
<showWeaveInfo>true</showWeaveInfo>
<forceAjcCompile>true</forceAjcCompile>
<sources>
<source>
<basedir>${project.build.directory}/generated-sources/delombok</basedir>
</source>
</sources>
<!--
<weaveDirectories>
<weaveDirectory>${project.build.directory}/classes</weaveDirectory>
</weaveDirectories>
-->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.2</version>
<scope>compile</scope>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.6.2</version>
<scope>compile</scope>
</dependency>
-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
</project>
package org.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public #interface Verify {
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#interface Access {}
}
package org.example;
import lombok.Data;
import org.springframework.stereotype.Service;
#Service
#Data
public class MyUtility {
Object info;
}
package org.example;
import lombok.Getter;
//import javax.persistence.Entity;
//import javax.persistence.Id;
import java.util.UUID;
//#Entity
public class Item {
// #Id
#Getter
private String uuid = UUID.randomUUID().toString();
private String name;
public Item(String name) {
this.name = name;
}
#Verify.Access
public String getName() {
return name;
}
}
package org.example;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
#Slf4j
public class MyAspect {
#Before("#annotation(Verify.Access)")
public void beforeAnnotation(JoinPoint joinPoint) {
log.error("BEFORE ANNOTATION");
}
}
package org.example;
import lombok.NonNull;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
#Aspect
public class OtherAspect {
#Autowired
private MyUtility myUtility;
// #Around("#annotation(SystemCall)")
#Around("#annotation(Verify.Access)")
public Object run(#NonNull final ProceedingJoinPoint join) throws Throwable {
return myUtility.getInfo();
// return join.proceed();
}
}
package org.example;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.Aspects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
#Configuration
#Slf4j
public class Main {
#Bean
public OtherAspect otherAspect() {
return Aspects.aspectOf(OtherAspect.class);
}
public static void main(String[] args) {
try (ConfigurableApplicationContext appContext = SpringApplication.run(Main.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) {
MyUtility myUtility = appContext.getBean(MyUtility.class);
myUtility.setInfo("my info");
Item item = new Item("my name");
log.info(item.getName());
}
}
If you run the Spring Boot application, you will see the following on the console (timestamps removed):
ERROR 20680 --- [ main] org.example.MyAspect : BEFORE ANNOTATION
INFO 20680 --- [ main] org.example.Main : my info
As you can see, both aspects kick in, the first one logging an ERROR and the other one changing the return value from "my name" to "my info".
The advantage of the "delombok" variant is that within the same Maven module, you can weave aspects into the Lombok-generated source code. The disadvantage is, that in your IDE you might not be able to compile the project imported from Maven because of the very unusual custom configuration. In IntelliJ IDEA, I had to delegate the build to Maven, but still the source code editor shows squiggly lines.
As an alternative, you could create one module with Lombok compilation (no "delombok") and a second module using binary weaving in order to weave aspects into the Lombok-enhanced class files, as described here. It would all be much easier without Lombok, though. The third alternative is compilation with Lombok and native AspectJ load-time weaving configured for Spring Boot instead of compile-time or binary weaving during build time. I cannot explain and show every variant in detail here, it is a long answer already.
I've got an up-the-middle Spring Boot + Lombok project that works like a champ from the IDE but errors strangely when I run it from the command line through mvn spring-boot:run. Its a pretty recent version Spring Boot...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Lombok is unremarkable and came from the Spring Initializr thing (https://start.spring.io/).
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
The JavaBean its complaining about is equally boring...
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
#Data
#Builder(toBuilder = true)
public class TemplateLineItemInput {
private final String partMasterId;
private final String partMasterIdPath;
private final String actionType;
private final BigDecimal actionQuantity;
}
The API of this Boot project is GraphQL but when I execute the following mutation from a mvn spring-boot:run invocation it always comes back as an error (nothing on the console...the framework is kicking it out somehow).
Request...
mutation createTemplateLineItem($tbomId: ID!) {
createTemplateLineItem(
tbomId: $tbomId
input: {
partMasterId: "2"
partMasterIdPath: "808863036.1"
actionType: "ADD"
actionQuantity: "2"
}) {
...TBomFrag
}
}
...
{
"partMasterId": "5025489768",
"tbomId": "a4688d22-9d99-41a2-9777-6acc75b2aab9",
"lineItemId": "9e460199-34fb-432c-b971-8cd8321d3283"
}
Response...
{
"errors": [
{
"message": "No primary or single public constructor found for class aero.blue.ems.boa.domain.TemplateLineItemInput - and no default constructor found either",
"locations": [
{
"line": 20,
"column": 3
}
],
"path": [
"createTemplateLineItem"
],
"extensions": {
"classification": "INTERNAL_ERROR"
}
}
],
"data": {
"createTemplateLineItem": null
}
}
My spring-boot-maven-plugin is configured like...
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.4</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>repackage</id>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
...also from Spring Initializr
When I run the Application from the IDE directly, no issue. The mutation works fine.
Is there something missing from my spring-boot:run config in the pom.xml or something? Did I have to clue the plugin into annotation processing? This is really confusing.
Please,
Check the following config on section plugins at you pom.xml project, as following here:
<project>
<modelVersion>4.0.0</modelVersion>
<artifactId>getting-started</artifactId>
<!-- ... -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
For more information about, check this link: https://docs.spring.io/spring-boot/docs/2.5.4/maven-plugin/reference/htmlsingle/
Ultimately this comes down to Lombok misconfiguration of my beans. The fix is to add the #AllArgsConstructor to the bean definition
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
#Data
#Builder(toBuilder = true)
#AllArgsConstructor
public class TemplateLineItemInput {
private final String partMasterId;
private final String partMasterIdPath;
private final String actionType;
private final BigDecimal actionQuantity;
}
How we figured this out was to "Delombok" the Bean and look at the resulting code. This observation matched the error message; there was no public constructor.
...
TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
this.partMasterId = partMasterId;
this.partMasterIdPath = partMasterIdPath;
this.actionType = actionType;
this.actionQuantity = actionQuantity;
}
Somehow (I still don't fully get why), the #Builder(toBuilder=true) annotation had Lombok producing a package private constructor. Jackson needed something public.
Adding the #AllArgsConstructor annotation made that constructor public and all is well.
public TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
this.partMasterId = partMasterId;
this.partMasterIdPath = partMasterIdPath;
this.actionType = actionType;
this.actionQuantity = actionQuantity;
}
Delombok was the key.
I have a case that i have 35 classes that some of them related with each other inside of them. Such as;
Addendum.java
#XmlType(name="addendum",namespace= GenericNameSpaceConstants.POLICY_NAMESPACE_URI)
#XmlAccessorType(XmlAccessType.FIELD)
public class Addendum implements Serializable {
#XmlElement(name="changeNumber",nillable=false,required=true)
private Long changeNumber;
#XmlElement(name="changeTypeDesc",nillable=false,required=true)
private String changeTypeDesc;
#XmlElement(name="changeTypeId",nillable=false,required=true)
private Integer changeTypeId;
}
Policy.java
#XmlRootElement(name="policy",namespace=GenericNameSpaceConstants.POLICY_NAMESPACE_URI)
#XmlType(name="policy",namespace= GenericNameSpaceConstants.POLICY_NAMESPACE_URI)
#XmlAccessorType(XmlAccessType.FIELD)
public class Policy {
#XmlElement(name="addendum",required=true,nillable=false)
private Addendum addendum;
}
My jaxb schemage config in pom file like that
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<createJavaDocAnnotations>false</createJavaDocAnnotations>
<sources>
<source>
${project.basedir}\src\main\java\com\aegon\common\service\bean\
</source>
</sources>
<verbose>true</verbose>
<outputDirectory>${basedir}/src/main/resources/schemas</outputDirectory>
<transformSchemas>
<transformSchema>
<toPrefix>pol</toPrefix>
<toFile>policy_model_v2.xsd</toFile>
</transformSchema>
</transformSchemas>
<generateEpisode>true</generateEpisode>
</configuration>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>schemagen</goal>
</goals>
</execution>
</executions>
</plugin>
When i run the project for phase generate-resources or generate-sources. I am getting this error Addendum is a non-static inner class, and JAXB can't handle those.
How can i resolve this problem?? How can i generate all classes xsd in a simple xsd Or how can i create xsds' one by one and import to complex one
I have found the problem. every class need a default constructor
I am trying to use drools with Spring in a Maven Eclipse project.
1.My POM has the following
<properties>
<droolsVersion>5.5.0.Final</droolsVersion>
</properties>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${droolsVersion}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${droolsVersion}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.sample.app.Test2</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>jboss</id>
<name>jboss</name>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
</repositories>
2.My sample class is here
package com.services.dms.model;
public class Account {
private Integer balance;
public Account() {}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
public Account(Integer balance) {
super();
this.balance = balance;
}
public void withdraw(int money) {
balance -= money;
}
}
My test main class
package com.services.dms.service;
import org.drools.KnowledgeBase;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatelessKnowledgeSession;
import com.services.dms.model.Account;
public class Test2 {
public static final void main(String[] args) {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource("testrule.drl"), ResourceType.DRL);
KnowledgeBase kbase = kbuilder.newKnowledgeBase();
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
Account account = new Account(200);
account.withdraw(150);
ksession.execute(account);
}
}
And my testrule.drl is
package com.services.dms.service
import com.services.model.account;
rule "accountBalanceAtLeast"
when
$account : Account( balance < 100 )
then
System.out.println("Warning! money running out!");
end
So when I run this project i get a file not found exception and it is not able to find the .drl file.
DRL file stored in src->resources->rules
Even When I put the rule file in the same package as my test main class I get the same error.
A classpath resource is expected in one of the directories that are collected in it. Most likely src/resources/rules is not part of the classpath. I don't know what you mean by "put the file in the same package as my test main class", but chances are that you just change the package statement of the DRL file.
Think how com.services.dms.model.Account is found by the JVM: the directory where com.services.dms.model.Account is rooted must be part of the classpath. So: put your DRL file there and expect it to be found. Conversely, prefix some directory levels to the filename in your Test2.main.
Always, always, always call the KnowledgeBuilder methods hasErrors and getErrors after adding a resource, e.g.:
if( kbuilder.hasErrors() ){
System.err.println( "### compilation errors ###" );
KnowledgeBuilderErrors errors = kbuilder.getErrors();
for( KnowledgeBuilderError err: errors ){
System.err.println( err.toString() );
}
throw new IllegalStateException( "compile errors" );
}
This will show you clearly where the problem in your DRL file is. (A double fault ;-) )
I have a library Common.License which I am obfuscating with Proguard:
<plugin>
<groupId>com.pyx4me</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<obfuscate>true</obfuscate>
<options>
<option>-dontoptimize</option>
<option>-renamesourcefileattribute SourceFile</option>
<option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod</option>
<option>-keep public class * { public protected *;}</option>
<option>-keepclassmembernames class * {java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String, boolean);}</option>
<option>-keepclassmembernames class * {com.common.license.LicenseSessionStore licenseSessionStore; com.common.license.LicenseStore licenseStore;}</option>
<option>-keepclassmembers enum * {public static **[] values(); public static ** valueOf(java.lang.String);}</option>
<option>-keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve();}</option>
</options>
<libs>
<lib>${java.home}/lib/rt.jar</lib>
<lib>${java.home}/lib/jsse.jar</lib>
</libs>
<addMavenDescriptor>false</addMavenDescriptor>
</configuration>
</plugin>
This library has a Spring bean annotated with #Service:
#Service
public class LicenseServiceImpl implements LicenseService {
#Autowired(required = false)
LicenseSessionStore licenseSessionStore;
#Autowired(required = false)
LicenseStore licenseStore;
...
}
I use this library in a web service Company.License where I want the LicenseService to autowire:
#Component
public class BackgroundTasks {
#Autowired
ScheduledExecutorService scheduledExecutorService;
#Autowired
LicenseService licenseService;
...
}
So Company.License has a dependency on Common.License. If I obfuscate Common.License then licenseService will not autowire in BackgroundTasks. The only way I could work around this was to define licenseService explicitly as a bean:
#Bean(name = "licenseService", autowire = Autowire.BY_NAME)
public LicenseService getLicenseService() {
if (licenseService == null) {
licenseService = new LicenseServiceImpl();
}
return licenseService;
}
I should not need to explicitly declare this as a bean like this as I have already annotated the class with #Service which should be enough to make the autowiring of licenseService in BackgroundTasks Spring-magically work. But it doesn't!
What is Proguard specifically doing to make this not work? Is there anything I can do in the configuration of Proguard to make it more Spring friendly?
Grant
A big thank you to Eric Lafortune for helping point me in the correct direction here:
http://sourceforge.net/projects/proguard/forums/forum/182456/topic/2547498
Here's the working pom file addition specifying the plug-in and the options required:
<plugin>
<groupId>com.pyx4me</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<obfuscate>true</obfuscate>
<options>
<option>-dontoptimize</option>
<option>-keepdirectories</option>
<option>-renamesourcefileattribute SourceFile</option>
<option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod</option>
<option>-keep public class * { public protected *;}</option>
<option>-keepclassmembernames class * {java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String, boolean);}</option>
<option>-keepclassmembernames class * {com.common.license.LicenseService licenseService; com.common.license.LicenseSessionStore licenseSessionStore;}</option>
<option>-keepclassmembers enum * {public static **[] values(); public static ** valueOf(java.lang.String);}</option>
<option>-keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve();}</option>
<option>-keep #org.springframework.beans.factory.annotation.Service class *</option>
<option>-keep #org.springframework.stereotype.Controller class *</option>
<option>-keepclassmembers class * { #org.springframework.beans.factory.annotation.Autowired *; }</option>
</options>
<libs>
<lib>${java.home}/lib/rt.jar</lib>
<lib>${java.home}/lib/jsse.jar</lib>
</libs>
<addMavenDescriptor>false</addMavenDescriptor>
</configuration>
</plugin>
NOTE! You need to use ProGuard 4.4, the latest version of ProGuard-Maven-Plugin (2.0.4) uses 4.3 so you need to edit:
{M2_HOME}\repository\com\pyx4me\proguard-maven-plugin\2.0.4\proguard-maven-plugin-2.0.4.pom
To have the 4.4 dependency (like this):
<dependency>
<groupId>net.sf.proguard</groupId>
<artifactId>proguard</artifactId>
<version>4.4</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
Here's another way to switch to version 4.4 of proguard (which is probably a bit better):
<configuration>
<proguardVersion>4.4</proguardVersion>
<obfuscate>true</obfuscate>
<options>
...
</options>
<libs>
<lib>${java.home}/lib/rt.jar</lib>
<lib>${java.home}/lib/jsse.jar</lib>
</libs>
<addMavenDescriptor>false</addMavenDescriptor>
</configuration>
<dependencies>
<dependency>
<groupId>net.sf.proguard</groupId>
<artifactId>proguard</artifactId>
<version>4.4</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
I had the same issue and the -keepdirectories directive helped solve the issue for me.