I have a custom code generator extending JavaGenerator and it would be very useful if the user using this generator could specify additional information like a list of column names to which the custom generator applies to.
My first though would be to add configuration options to the dependency with mojos.
However this does not seem to work properly because during the build cycle two separate instance are created, once from maven and once from JOOQ.
This is my custom generator:
#Mojo(name="generation")
public class CustomGenerator extends JavaGenerator implements org.apache.maven.plugin.Mojo, ContextEnabled {
#Parameter(property="tableNames")
private List tableNames;
private static final Logger log = LoggerFactory.getLogger(CustomGenerator.class);
#Override
protected void generateSchema(SchemaDefinition schema) {
//custom code generation based on the variable "tableNames"
}
#Override
public void execute() throws MojoExecutionException, MojoFailureException {
//called when maven instantiates this class
}
//bunch of empty methods I do not care about but have to be there because
//I cannot let this class also extend from AbstractMojo
#Override
public void setPluginContext(Map map) {
}
#Override
public Map getPluginContext() {
return null;
}
#Override
public void setLog(Log log) {
}
#Override
public Log getLog() {
return null;
}
}
And this is my pom of the project where I use the generator and want to supply additional information:
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>3.15.4</version>
<configuration>
<generator>
<name>org.example.CustomGenerator</name>
</generator>
</configuration>
</plugin>
<plugin>
<groupId>org.example</groupId>
<artifactId>customgenerator</artifactId>
<version>1.0</version>
<configuration>
<tableNames>
<tableName>test_table</tableName>
</tableNames>
</configuration>
<executions>
<execution>
<goals>
<goal>generation</goal>
</goals>
</execution>
</executions>
</plugin>
...
<dependency>
<groupId>org.example</groupId>
<artifactId>customgenerator</artifactId>
<version>1.0</version>
</dependency>
If there are any other methods to supply custom information to the generator, please let me know.
Using database properties
Maybe, much simpler, use the properties available in database as follows:
<configuration>
<generator>
<database>
<properties>
<property>
<key>some_key</key>
<value>some_value</value>
</property>
</properties>
</database>
</generator>
</configuration>
There's no specification what you place as key/value in those properties. They've been added for purposes like yours, e.g. to add custom configuration to advanced <database> implementations like:
DDLDatabase
XMLDatabase
LiquibaseDatabase
JPADatabase
See also those sections to see how the properties are used by those databases.
Your custom JavaGenerator logic can then access the properties via:
Properties properties = definition.getDatabase().getProperties();
Where definition is any object that is being generated, e.g. SchemaDefinition in your code.
Simplest solution
Of course, setting system properties is always a pragmatic option.
Related
I am building a back-end rest api with spring boot.
Entity:
#Entity
public class Club {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
#Column(unique=true)
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Repository:
#RepositoryRestResource
public interface ClubRepository extends JpaRepository<Club, Long>, JpaSpecificationExecutor<Club> {
}
This alone exposes a rest endpoint at http://host/clubs, great. Now, I'd like to allow some parameters in the url for search purposes, so I started following the instructions of http://www.baeldung.com/rest-api-search-language-spring-data-specifications.
But they end-up creating a custom #Controller to pass the request params:
#Controller
public class ClubController {
#Autowired
private ClubRepository repo;
#RequestMapping(method = RequestMethod.GET, value = "/clubs")
#ResponseBody
public List<Club> search(#RequestParam(value = "search") String search) {
/* ... */
return repo.findAll(spec);
}
}
So you see, they end up calling the findAll method of the repository, just passing a specification they build based on the query parameter. Easy enough, but really I'd love to not have to create additional controllers for each of my domain objects. In other words, is there a way to provide this search feature by directly annotating (for example) the #Entity, or overriding methods (like the findAll method) in the repository?
A better approach is to use QueryDsl rather than the criteria API. Spring Data and, by extension, Spring Data Rest will then give you a lot of excellent functionality 'for free' other than some basic configuration.
https://spring.io/blog/2015/09/04/what-s-new-in-spring-data-release-gosling#spring-data-rest
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
One configured your repository interface simply becomes:
#RepositoryRestResource
public interface ClubRepository extends JpaRepository<Club, Long>, QueryDslPredicateExecutor<Club> {
}
An endpoint can then be queried using a dynamic combination of parameters:
e.g.
http://host/clubs?name=someName
http://host/clubs?name=someName&otherProperty=X&sort=name,desc
No further code is required for this i.e. no custom controller, no specifications and no query methods.
For configuring Maven simply add the following to the POM in the module containing your entities: this will generate the necessary 'query' classes.
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
I would like to introduce Byte Buddy to my company and I have prepared a demo for my colleagues. Since we use Spring a lot, I thought the best example would be instrumentation of SpringBoot application. I have decided to add logs to RestController methods.
Instrumented application is a simple SpringBoot Hello World example:
#RestController
public class HelloController {
private static final String template = "Hello, %s!";
#RequestMapping("/hello")
public String greeting(
#RequestParam(value = "name", defaultValue = "World") String name) {
return String.format(template, name);
}
#RequestMapping("/browser")
public String showUserAgent(HttpServletRequest request) {
return request.getHeader("user-agent");
}
}
And here is my Byte Buddy agent:
public class LoggingAgent {
public static void premain(String agentArguments,
Instrumentation instrumentation) {
install(instrumentation);
}
public static void agentmain(String agentArguments,
Instrumentation instrumentation) {
install(instrumentation);
}
private static void install(Instrumentation instrumentation) {
createAgent(RestController.class, "greeting")
.installOn(instrumentation);
}
private static AgentBuilder createAgent(
Class<? extends Annotation> annotationType, String methodName) {
return new AgentBuilder.Default().type(
ElementMatchers.isAnnotatedWith(annotationType)).transform(
new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader) {
return builder
.method(ElementMatchers.named(methodName))
.intercept(
MethodDelegation
.to(LoggingInterceptor.class)
.andThen(
SuperMethodCall.INSTANCE));
}
});
}
}
Interceptor logs method execution:
public static void intercept(#AllArguments Object[] allArguments,
#Origin Method method) {
Logger logger = LoggerFactory.getLogger(method.getDeclaringClass());
logger.info("Method {} of class {} called", method.getName(), method
.getDeclaringClass().getSimpleName());
for (Object argument : allArguments) {
logger.info("Method {}, parameter type {}, value={}",
method.getName(), argument.getClass().getSimpleName(),
argument.toString());
}
}
When executed with -javaagent parameter this example works well. When however I try to load the agent on the running JVM with Attach API:
VirtualMachine vm = VirtualMachine.attach(args[0]);
vm.loadAgent(args[1]);
vm.detach();
I've got the following exception on the first logging attempt:
Exception in thread "ContainerBackgroundProcessor[StandardEngine[Tomcat]]" java.lang.IncompatibleClassChangeError: Class ch.qos.logback.classic.spi.ThrowableProxy does not implement the requested interface ch.qos.logback.classic.spi.IThrowableProxy
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.subjoinExceptionMessage(ThrowableProxyConverter.java:180)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.subjoinFirstLine(ThrowableProxyConverter.java:176)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.recursiveAppend(ThrowableProxyConverter.java:159)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.throwableProxyToString(ThrowableProxyConverter.java:151)
at org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter.throwableProxyToString(ExtendedWhitespaceThrowableProxyConverter.java:35)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.convert(ThrowableProxyConverter.java:145)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.convert(ThrowableProxyConverter.java:1)
at ch.qos.logback.core.pattern.FormattingConverter.write(FormattingConverter.java:36)
at ch.qos.logback.core.pattern.PatternLayoutBase.writeLoopOnConverters(PatternLayoutBase.java:114)
at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:141)
at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:1)
at ch.qos.logback.core.encoder.LayoutWrappingEncoder.doEncode(LayoutWrappingEncoder.java:130)
at ch.qos.logback.core.OutputStreamAppender.writeOut(OutputStreamAppender.java:187)
at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:212)
at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:100)
at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:48)
at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)
at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421)
at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
at ch.qos.logback.classic.Logger.log(Logger.java:765)
at org.slf4j.bridge.SLF4JBridgeHandler.callLocationAwareLogger(SLF4JBridgeHandler.java:221)
at org.slf4j.bridge.SLF4JBridgeHandler.publish(SLF4JBridgeHandler.java:303)
at java.util.logging.Logger.log(Unknown Source)
at java.util.logging.Logger.doLog(Unknown Source)
at java.util.logging.Logger.logp(Unknown Source)
at org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:181)
at org.apache.juli.logging.DirectJDKLog.error(DirectJDKLog.java:147)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1352)
at java.lang.Thread.run(Unknown Source)
I run the example on 64-bit HotSpot with Java8:
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)
Byte Buddy version is 1.4.32. Here is agent maven configuration:
<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>pl.halun.demo.bytebuddy</groupId>
<artifactId>byte-buddy-agent-demo</artifactId>
<version>1.0</version>
<properties>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.4.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>${project.artifactId}-${project.version}-full</finalName>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifestEntries>
<Premain-Class>pl.halun.demo.bytebuddy.logging.LoggingAgent</Premain-Class>
<Agent-Class>pl.halun.demo.bytebuddy.logging.LoggingAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
And here is pom file for the instrumented application:
<?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>pl.halun.demo.bytebuddy.instrumented.app</groupId>
<artifactId>byte-buddy-agent-demo-instrumented-app</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
From my point of view it is very valuable option to add logs on the running server and I hate to loose this part of demo. I tried to experiment with different redefinition strategies, but until now nothing seems to work.
What you observe is a classical version conflict, I think. Spring Boot most likely comes with a version of ThrowableProxy that is not compatible to the version that is added with the Java agent. When loading a Java agent at runtime, Spring's version is already loaded whereas the startup attachment prepends the agent-bundled version on the class path where the version of your agent is loaded.
Java agents are typically added to the class path. This is also where your Spring boot application is living. You need to make sure that a Java agent does not contain dependencies that are incompatible with your application's dependencies or you need to shade all dependencies to avoid such conflicts.
There is however another problem: When writing a Java agent that is attached at runtime, you meet additional constraints on most JVMs where on HotSpot, you are not allowed to change the class file format of any class that is already loaded. There is also a chance that your class is already loaded where currently, no effect would be visible as you do not enable retransformation.
A runtime-capable agent would need to use the Advice component which inlines code into target code rather then using the classical delegation model:
class MyAdvice {
#Advice.OnMethodEnter
static void intercept(#Advice.BoxedArguments Object[] allArguments,
#Advice.Origin Method method) {
Logger logger = LoggerFactory.getLogger(method.getDeclaringClass());
logger.info("Method {} of class {} called", method.getName(), method
.getDeclaringClass().getSimpleName());
for (Object argument : allArguments) {
logger.info("Method {}, parameter type {}, value={}",
method.getName(), argument.getClass().getSimpleName(),
argument.toString());
}
}
}
You can use the above advice class by registering it as a visitor. Such visitors only apply to declared methods, i.e. not to inherited methods and inline their code into existing methods. This way, the logging will not be visible on the call stack and it also becomes legal to retransform already loaded classes:
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(isAnnotatedWith(annotationType))
.transform(new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader) {
return builder.visit(Advice.to(MyAdvice.class).on(named(methodName)));
}
});
As for the attachment, look into the byte-buddy-agent project which allows you to call:
ByteBuddyAgent.attach(agentJar, processId);
The above helper supports other VMs where the attachment API often lives in a different namespace.
Update: Here is the problem with Spring Boot. Spring Boot creates custom class loaders that have the system class loader (class path) as their parent. These class loaders consider classes from the system class loader first. When you add the agent, the entire Spring boot app is both on the class loader and in these child class loaders. A class like IThrowableProxy now exists twice in two class loaders but is not considered to be equal by the JVM. Depending on the state of the VM, some classes might already be linked to the original IThrowableProxy whereas other classes are loaded after the agent was attached and get linked to the new IThrowableProxy from the agent. Both classes are not equal and the error that you see is thrown where the VM complains that the class does not implement the correct IThrowableProxy (but the previous one). If the agent is attached at start up, this problem does not exist as the class path's IThrowableProxy is always loaded.
This is not an easy error to fix, in the end, Byte Buddy cannot help you with such class path issues and Spring Boot is quite free in its interpretation of the class loader contract. The easiest way would be to not use Spring Boot types in your agent. You can still match the annotation with for example
isAnnotatedWith(named("org.springframework.web.bind.annotation.RestController"))
The question is how you can communicate with Spring Boot. One work-arround would be to add all shared classes to the class path on start up. Typically, I do avoid the usage of shared classes altogether but only use them in the Advice classes where the code is inlined in the class loader of the target application. Simply set the Spring Boot dependency in provided scope, the advice code itself is never executed.
I generated my classes with jaxb and now I need to populate some list. What's the best way to do that?
pom.xml:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory>${basedir}/src/main/resources/META-INF/xsd</schemaDirectory>
<packageName>be.structure</packageName>
<outputDirectory>${basedir}/target/generated/java</outputDirectory>
</configuration>
</plugin>
The generated class where the list is located:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "configuration", propOrder = {
"professions"
})
public class Configuration {
protected List<Profession> professions;
public List<Profession> getProfessions() {
if (professions == null) {
professions = new ArrayList<Profession>();
}
return this.professions;
}
}
but as you can see there is no "addProfession" or "setProfessions()" or something. I know there is a way, but I can't really remember it..
getProfessions().add(profession) should do the trick if the underlying list is mutable, but normally you wouldn't change the contents of JAX-instances since JAXB populates the objects for you based on the XML data it is read from - if you change that lists, its no representation of the XML anymore.
I'm having a very strange issue in my java application. I'm building it with maven and developing it in Eclipse IDE. This might be a bit lengthy explanation but please stick to the end of it, cause the problem is really strange and I have no clue what can be the cause of it.
Here's an example of code I'm writing:
Suppose we have a "Handler" interface. It can handle a specific object type:
public interface Handler<T> {
public void handle(T obj);
}
Now let's say we want to have Handler chaining. We could do it like this:
public class HandlerChain<T> implements Handler<T> {
private Handler<? super T> h;
#Override
public void handle(T obj) {
//h can handle T objects
h.handle(obj);
}
private HandlerChain(Handler<? super T> h) {
super();
this.h = h;
}
//syntax sugar to start the chain
public static <T> HandlerChain<T> start(Handler<? super T> h){
return new HandlerChain<T>(h);
}
//add another handler to the chain
public HandlerChain<T> next(final Handler<? super T> handler){
return new HandlerChain<T>(new Handler<T>() {
#Override
public void handle(T obj) {
h.handle(obj);
handler.handle(obj);
}
});
}
}
Now let's make some handler factories for, say, String handlers:
public class Handlers {
public static Handler<String> h1(){
return new Handler<String>(){
#Override
public void handle(String obj) {
// do something
}
};
}
public static Handler<String> h2(){
return new Handler<String>(){
#Override
public void handle(String obj) {
// do something
}
};
}
}
So finally we make a class that handles some Strings using the two handlers in a chain:
public class Test {
public void doHandle(String obj){
HandlerChain.start(Handlers.h1()).next(Handlers.h2()).handle(obj);
}
}
So, to me there seemed nothing wrong with this code. Eclipse IDE didn't mind either. It even ran it correctly. But when I tried to compile this code with maven from cli, I got an error:
Test.java:[7,50] next(Handler<? super java.lang.Object>) in HandlerChain<java.lang.Object> cannot be applied to (Handler<java.lang.String>)
Had anyone stumbled upon similar problems? I would really like to know if this kind of syntax is valid in java or is this some strange compiler bug due to bad setup or something? To repeat Eclipse compiles AND runs this code correctly, but maven cli cannot compile it.
Finally, here's my maven-compiler-plugin settings in pom.xml. It might be relevant to the whole issue.
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<executions>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<source>1.6</source>
<target>1.6</target>
</configuration>
</execution>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<source>1.6</source>
<target>1.6</target>
</configuration>
</execution>
</executions>
<configuration>
<encoding>UTF-8</encoding>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
Thanks in advance!
Had anyone stumbled upon similar problems?
Yes, and also when doing weird and unusual (probably incorrect) stuffs with generic types.
The problem was that the eclipse compiler don't report a compilation error on those strange construct while the standard javac from Sun JDK complains about type erasure. (it was with JDK 1.6). (If I remember well: eclipse report a only a warning)
My solution was to setup maven to use eclipse compiler. The other (better) option was to fix the code, but since it was a quite complex task and since I didn't have any issue at runtime I choose the "quick and dirty" first option.
Here is how to setup the compiler:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${compiler.source}</source>
<target>${compiler.target}</target>
<encoding>${source.encoding}</encoding>
<fork>false</fork>
<compilerId>jdt</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<version>0.13.0</version>
</dependency>
</dependencies>
</plugin>
I'm learning Spring DM and I have a problem with Service registry. I'm using ServiceMix 4.3.0 with embedded Felix instance. I have 2 bundles in my project.
The first one contains interface, and mock implementation. I want to publish them to OSGi service registry:
public interface PersonAPI {
public void listAll();
public List<PersonEntity> getAll();
public void addPerson(PersonEntity pe);
public PersonEntity getPerson(int id);
}
PersonEntity is a simple class with data, nothing special.
Mock implementation is contains just a list of PeopleEntity objects, so there is nothing interesting either.
Here is part of Spring configuration XML:
<bean id="personImpl" class="com.osgi.Person.impl.mock.PersonImpl" />
<osgi:service id="personLogic" ref="personImpl" interface="com.osgi.Person.PersonAPI" />
And part taken from pom.xml file:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.4</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Category>sample</Bundle-Category>
<Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
<Export-package>com.osgi.*</Export-package>
</instructions>
</configuration>
</plugin>
This installs fine on ServiceMix. Now I defined another bundle, here are most important parts:
public class PersonTester {
PersonAPI api;
public void init() {
System.out.println("PostConstruct:");
System.out.println("Have API " + api + " class " + api.getClass().getCanonicalName());
api.listAll(); //This line (or any API call, blows everything)
}
public PersonAPI getApi() {
return api;
}
public void setApi(PersonAPI api) {
this.api = api;
}
}
Spring configuration:
<osgi:reference id="personLogic" interface="com.osgi.Person.PersonAPI" />
<bean id="personTester" init-method="init" class="com.osgi.Person.PersonTester">
<property name="api" ref="personLogic" />
</bean>
Most important parts from pom.xml:
<dependencies>
<dependency>
<groupId>com.osgi</groupId>
<artifactId>Pserson-API</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
....
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.4</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Category>sample</Bundle-Category>
<Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
<Import-package>com.Person.entity,com.osgi.Person</Import-package>
</instructions>
</configuration>
</plugin>
The good news is, that "behind" injected Spring proxy is my implementation class. I can see that when using api.toString(). However when I call any method defined in my proxy, I get an exception:
Exception in thread "SpringOsgiExtenderThread-88" org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'personTester': Invocation of init method failed; nested exception is org.springframework.aop.AopInvocationException: AOP configuration seems to be invalid: tried calling method [public abstract void com.osgi.Person.PersonAPI.listAll()] on target [PersonImpl [set=[]]]; nested exception is java.lang.IllegalArgumentException:
object is not an instance of declaring class
It looks like It looks like AOP is missing the target, but why? And how to fix this?
The problem is fixed. It was something in the environment.
I started building new project (with different artifact Id) on top of this tutorial and everything looks OK. Perhaps something cached somewhere (strange, because I flushed ServiceMix bundle cache and local maven repo). I try to reproduce it on different machine, and maybe I'll come up with some better explanation.