Difference between revisions of "How to Create a new OSGi Bundle"
Line 58: | Line 58: | ||
</pre> | </pre> | ||
− | + | Usually, I would be finished here. I've created a bundle, put code there, made a POM file for it, and then added it to its parent. | |
+ | |||
+ | This particular case, however, is more complex. You'll see why when we try to build it: | ||
<pre> | <pre> | ||
$ mvn clean install | $ mvn clean install | ||
Line 77: | Line 79: | ||
</pre> | </pre> | ||
− | + | What happened is that the bundle where we moved <tt>Note.java</tt> was still looking for it. In fact, if you look at <tt>notes/api/services/pom.xml, you will notice that it is listing <tt>org.opentaps.notes.domain</tt> as one of the packages that it exports: | |
− | |||
− | What happened is that the bundle where we moved <tt>Note.java</tt> was still looking for it | ||
<pre> | <pre> | ||
Revision as of 00:06, 25 February 2012
While writing Customizing opentaps 2: an OSGi Tutorial, I realized that one of the Java classes was in the wrong place. Note.java, which is the object representation of a note, is a domain class under the Domain Driven Architecture. Since domain classes represent data, not actions, it shouldn't be part of the services API bundle, so I'm going to move it into a new bundle for a domain class. In the process, I thought this would be a good tutorial on how to create an OSGi bundle.
First, I create a directory for my bundle. Under the opentaps-2/modules/notes, I will add a new directory called domain, parallel to api and impl.
Next, I create a directory within this bundle for my Java files: src/main/java under the opentaps-2/modules/notes/domain directory. This is where the Java source files go. If there were other types of source files, like web.xml for webapp configuration or .properties files for localizations, then I would also need a src/main/resources, but I don't have those right now, so I'm not going to create this.
Next, I need a pom.xml for maven in the domain/ directory. I will borrow from the one for the api bundle and modify it:
<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> <artifactId>notes.domain</artifactId> <name>Notes domain</name> <packaging>bundle</packaging> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> </plugin> </plugins> </build> <parent> <groupId>org.opentaps</groupId> <version>2.0.1-SNAPSHOT</version> <artifactId>notes</artifactId> </parent> </project>
Let's look at this pom.xml. There are a few things you should notice:
- The packaging is bundle because we're trying to get a bundle built, whether it's a jar or war file. If we're just using this to manage other bundles, then it would be pom.
- It does not have its own groupId or version because they are the same as the one for the parent. Putting these tags again would cause an error when you try to build.
We'll also want to add our new bundle to its parent, notes/pom.xml:
<modules> <module>domain</module> <module>api</module> <module>impl</module> <module>rest</module> <module>rest_ru</module> <module>rest_fr</module> <module>eba</module> </modules>
Now my notes/domain bundle is set up. I create the directory structure and move my Note.java over:
$ git mv modules/notes/api/services/src/main/java/org/opentaps/notes/domain/Note.java modules/notes/domain/src/main/java/org/opentaps/notes/domain/
Usually, I would be finished here. I've created a bundle, put code there, made a POM file for it, and then added it to its parent.
This particular case, however, is more complex. You'll see why when we try to build it:
$ mvn clean install ... [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile (default-compile) on project notes.services.api: Compilation failure: Compilation failure: [ERROR] /Users/sichen/git/opentaps-2/modules/notes/api/services/src/main/java/org/opentaps/notes/services/GetNoteByIdServiceOutput.java:[20,32] package org.opentaps.notes.domain does not exist [ERROR] /Users/sichen/git/opentaps-2/modules/notes/api/services/src/main/java/org/opentaps/notes/services/GetNoteByIdServiceOutput.java:[24,12] cannot find symbol [ERROR] symbol : class Note [ERROR] location: class org.opentaps.notes.services.GetNoteByIdServiceOutput [ERROR] /Users/sichen/git/opentaps-2/modules/notes/api/services/src/main/java/org/opentaps/notes/services/GetNoteByIdServiceOutput.java:[26,24] cannot find symbol [ERROR] symbol : class Note [ERROR] location: class org.opentaps.notes.services.GetNoteByIdServiceOutput [ERROR] /Users/sichen/git/opentaps-2/modules/notes/api/services/src/main/java/org/opentaps/notes/services/GetNoteByIdServiceOutput.java:[27,11] cannot find symbol [ERROR] symbol : class Note [ERROR] location: class org.opentaps.notes.services.GetNoteByIdServiceOutput [ERROR] -> [Help 1]
What happened is that the bundle where we moved Note.java was still looking for it. In fact, if you look at notes/api/services/pom.xml, you will notice that it is listing org.opentaps.notes.domain as one of the packages that it exports:
<Export-Package> org.opentaps.notes.services, org.opentaps.notes.services.security, org.opentaps.notes.domain </Export-Package>
So first I need to remove that, since the package is no longer there.
Then, I need to add to a dependency from the notes/api/services bundle to our new notes/domain bundle by modifying the notes/api/services/pom.xml:
<dependencies> ... <dependency> <groupId>${project.groupId}</groupId> <artifactId>notes.domain</artifactId> <version>${project.version}</version> <scope>provided</scope> </dependency> </dependencies>
I also added this to the other bundles which were using Note.java. Since this is a domain class, it was used throughout the application, so there were many bundles which I had to change. Finally, though, they were all fixed, and I get a different build error message:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:testCompile (default-testCompile) on project tests-run: Compilation failure: Compilation failure: [ERROR] /Users/sichen/git/opentaps-2/integration-tests/tests-run/src/test/java/org/opentaps/testsuit/notes/NotesTests.java:[27,32] package org.opentaps.notes.domain does not exist [ERROR] /Users/sichen/git/opentaps-2/integration-tests/tests-run/src/test/java/org/opentaps/testsuit/notes/NotesTests.java:[125,12] cannot find symbol [ERROR] symbol : class Note [ERROR] location: class org.opentaps.testsuit.notes.NotesTests [ERROR] /Users/sichen/git/opentaps-2/integration-tests/tests-run/src/test/java/org/opentaps/testsuit/notes/NotesTests.java:[80,8] cannot find symbol [ERROR] symbol : class Note [ERROR] location: class org.opentaps.testsuit.notes.NotesTests [ERROR] /Users/sichen/git/opentaps-2/integration-tests/tests-run/src/test/java/org/opentaps/testsuit/notes/NotesTests.java:[134,16] cannot find symbol [ERROR] symbol : class Note [ERROR] location: class org.opentaps.testsuit.notes.NotesTests [ERROR] /Users/sichen/git/opentaps-2/integration-tests/tests-run/src/test/java/org/opentaps/testsuit/notes/NotesTests.java:[134,46] cannot find symbol [ERROR] symbol : class Note [ERROR] location: class org.opentaps.testsuit.notes.NotesTests [ERROR] -> [Help 1]
This is because in this last bundle integration-tests/tests-run, we need to reference the groupId explicitly when we import the dependency to our bundle:
<dependency> <groupId>org.opentaps</groupId> <artifactId>notes.domain</artifactId> <version>${project.version}</version> <scope>provided</scope> </dependency>
Why? Read its pom.xml:
<parent> <artifactId>integration-tests</artifactId> <groupId>org.opentaps.testsuit</groupId> <version>2.0.1-SNAPSHOT</version> </parent>
It belongs to a different group!
With this correction, we finally get:
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 32.930s [INFO] Finished at: Fri Feb 24 15:26:18 PST 2012 [INFO] Final Memory: 25M/81M [INFO] ------------------------------------------------------------------------
But wait -- there's more! We also got a jar file at modules/notes/domain/target/org.opentaps.notes.domain-2.0.1-SNAPSHOT.jar, and in that jar file you will find a MANIFEST.MF which contains:
Manifest-Version: 1.0 Export-Package: org.opentaps.notes.domain Built-By: sichen Tool: Bnd-1.15.0 Bundle-Name: Notes domain Created-By: Apache Maven Bundle Plugin Bundle-Copyright: Copyright (c) 2010-2011 Open Source Strategies, Inc. Bundle-Vendor: Open Source Strategies Build-Jdk: 1.6.0_29 Bundle-Version: 2.0.1.SNAPSHOT Bnd-LastModified: 1330125965050 Bundle-ManifestVersion: 2 Bundle-SymbolicName: org.opentaps.notes.domain Bundle-DocURL: http://www.opensourcestrategies.com/
This has turned a standard Java jar into an OSGi bundle. The key part is Export-Package: org.opentaps.notes.domain, which means that it is exporting our org.opentaps.notes.domain package so other bundles can import it. Our bundle is very simple, and by default the bundle plugin (remember the <artifactId>maven-bundle-plugin</artifactId> from our pom.xml?) exports all packages in the bundle. You can, however, also specify <Export-Package>, <Private-Package>, <Import-Package> directives in pom.xml to control which packages are exported and which ones are not.
After I install it, I go to Geronimo to deploy my new EBA -- and got an error:
Caused by: org.apache.aries.application.management.ResolverException: The system cannot provision the EBA org.opentaps.notes.eba because the following problems in the dependency chain were detected: The package dependency org.opentaps.notes.domain required by bundle [org.opentaps.notes.repository.api_2.0.1.SNAPSHOT, org.opentaps.notes.repository.impl_2.0.1.SNAPSHOT, org.opentaps.notes.services.impl_2.0.1.SNAPSHOT, org.opentaps.notes.rest_2.0.1.SNAPSHOT, org.opentaps.notes.services.api_2.0.1.SNAPSHOT] cannot be resolved.
This is because the EBA doesn't have my new bundle. During the mvn clean install, maven showed me what it was putting in my EBA:
[INFO] --- eba-maven-plugin:0.3:eba (default-eba) @ tests-run --- [INFO] Copying artifact[org.opentaps, org.opentaps:core:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:entity:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:tests:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps.testsuit, org.opentaps.testsuit:tests-wab:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps.testsuit, org.opentaps.testsuit:tests-app:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:org.apache.xmlrpc:jar:3.0.0-v20100427-1100, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:org.apache.ws.commons.util:jar:1.0.1-v20100518-1140, compile] [INFO] Copying artifact[org.apache.commons, org.apache.commons:com.springsource.org.apache.commons.validator:jar:1.3.1, compile] [INFO] Copying artifact[org.apache.oro, org.apache.oro:com.springsource.org.apache.oro:jar:2.0.8, compile] [INFO] Copying artifact[org.apache.commons, org.apache.commons:com.springsource.org.apache.commons.httpclient:jar:3.1.0, compile] [INFO] Copying artifact[mysql, mysql:mysql-connector-java:jar:5.1.18, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:validation.api:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:validation.impl:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:rest:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:notes.rest:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:notes.services.api:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:notes.repository.api:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:notes.repository.impl:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.opentaps, org.opentaps:notes.services.impl:jar:2.0.1-SNAPSHOT, compile] [INFO] Copying artifact[org.restlet.jee, org.restlet.jee:org.restlet:jar:2.0.11, compile] [INFO] Copying artifact[org.restlet.jee, org.restlet.jee:org.restlet.ext.servlet:jar:2.0.11, compile] [INFO] Copying artifact[net.sourceforge.ezmorph, net.sourceforge.ezmorph:com.springsource.net.sf.ezmorph:jar:1.0.5, compile] [INFO] Copying artifact[org.apache.commons, org.apache.commons:com.springsource.org.apache.commons.beanutils:jar:1.8.0, compile] [INFO] Copying artifact[org.apache.commons, org.apache.commons:com.springsource.org.apache.commons.collections:jar:3.2.1, compile] [INFO] Copying artifact[org.apache.commons, org.apache.commons:com.springsource.org.apache.commons.logging:jar:1.1.1, compile] [INFO] Copying artifact[org.apache.commons, org.apache.commons:com.springsource.org.apache.commons.lang:jar:2.5.0, compile] [INFO] Copying artifact[net.sourceforge.json, net.sourceforge.json:com.springsource.net.sf.json:jar:2.2.2, compile] [INFO] Building zip: /Users/sichen/git/opentaps-2/integration-tests/tests-run/target/org.opentaps.testsuit.tests-run-2.0.1-SNAPSHOT.eba [INFO]
and org.opentaps.notes.domain wasn't in the list. So I need to go to notes.eba/pom.xml to add my dependency, and then the next time I see it:
[INFO] Copying artifact[org.opentaps, org.opentaps:notes.domain:jar:2.0.1-SNAPSHOT, compile]
Now Geronimo deploys the notes EBA, and I see org.opentaps.domain on the list of the OSGi bundles:
When I create a note from my HTML client, it works:
mysql> select note_id, note_text, client_domain from NOTE_DATA where date_time_created > '2012-02-24' order by sequence_num; +----------------------------------+--------------------------------------------------------+---------------+ | note_id | note_text | client_domain | +----------------------------------+--------------------------------------------------------+---------------+ | E20C2CF9868D44A7B05659B0203DBFBC | creating a note from the new org.opentaps.notes.domain | localhost | +----------------------------------+--------------------------------------------------------+---------------+ 1 row in set (0.00 sec)