How to Customize and Extend opentaps 2 with your own OSGi Bundle

From Opentaps Wiki
Revision as of 16:38, 8 March 2012 by Sichen (talk | contribs)
Jump to navigationJump to search

Ok, you've been patient and reading through all the other tutorials. But at some point, you've probably wondered -- what's the big idea here?

This tutorial will show you.

The most important thing for successful enterprise software is modularity: Separate and maintainable modules for each part of the application. OSGi lets you extend and customize your application with your own bundle. Even better, it lets you manage those bundles in real time, so you can manage your application by loading and unloading bundles from the Geronimo console.

In this tutorial, we will add security permissions to our opentaps 2 Notes application and then show you how to implement an OSGi bundle which overrides the base permission with your own. You can then see how you can deploy and undeploy the bundle in real time to change the security permissions of your system without restarting the server.

The first step is to create a new package, org.opentaps.notes.security, and a new interface NoteSecurity. All it does is define some operations and a security method:

public interface NoteSecurity {

    public static enum Operation {CREATE, UPDATE, DELETE};
    
    public boolean hasPermission(Note note, Operation permissionId);

    public String getErrorMessage();    
}

Then, I implement my security feature with a new module in notes/impl/security. I follow the steps from [[]] to create a new module. The code itself is trivial:

public class NoteSecurityImpl implements NoteSecurity {

    @Override
    public boolean hasPermission(Note note, String permissionId) {
        Log.logInfo("This test implementation will always return true, so I will let you create your note.");
        return true;
    }

}

Next, I modify my CreateNoteServiceImpl to check security before creating the Note:

        if (!security.hasPermission(note, NoteSecurity.Operation.CREATE)) {
            throw new ServiceException(security.getErrorMessage());
        }

But how do we get the security implementation into the service? This is done in an Inversion Of Control (IOC) pattern. First, we provide for the security with a hook in CreateNoteServiceImpl:

    private volatile NoteSecurity security = null;

    public void setNoteSecurity(NoteSecurity noteSecurity) {
        if (security == null && noteSecurity != null) {
            synchronized (this) {
                if (security == null) {
                    security = noteSecurity;
                }
            }
        }
    }

Now, we won't put anything in the code for where the security there. This is good, because when we change the security implementation later, we don't have to change the create note service. Instead, we'll use OSGI blueprint to wire the two together. I'll put some directives to modules/notes/impl/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml:

  <reference id="noteSecurityService" interface="org.opentaps.notes.security.NoteSecurity"/>

  <bean id="createNoteServiceImpl" class="org.opentaps.notes.services.impl.CreateNoteServiceImpl">
        <property name="noteSecurity" ref="noteSecurityService"/>
    </bean>   

What we're saying is that there is something called noteSecurityService, it is org.opentaps.notes.security.NoteSecurity, and we will use the CreateNoteServiceImpl.setNoteSecurity method to set it. This is equivalent to:

CreateNoteServiceImpl obj = new CreateNoteServiceImpl();
obj.setNoteRepository(InitialContext.lookup("org.opentaps.notes.repository.NoteRepository"));

where org.opentaps.notes.repository.NoteRepository in value of property osgi.jndi.service.name of the service.

In my security implementation module, I will also have a blueprint XML file, in modules/notes/impl/security/src/main/resources/OSGI-INF/blueprint/blueprint.xml. Here is where I define:

   <bean id="noteSecurityImpl" class="org.opentaps.notes.security.impl.NoteSecurityImpl">
   </bean>

   <service id="noteSecurityService" ref="noteSecurityImpl" interface="org.opentaps.notes.security.NoteSecurity">
        <service-properties>
            <entry key="osgi.jndi.service.name" value="org.opentaps.notes.security.NoteSecurity"/>
        </service-properties>
    </service>

Which says "I am implementing org.opentaps.notes.security.NoteSecurity with org.opentaps.notes.security.impl.NoteSecurityImpl." Note you have to define the entry key for osgi.jndi.service.name, or this name can be used to find service with service registry but not with JNDI lookup. Also, it is important to note that it is the full interface class name (org.opentaps.notes.security.NoteSecurity) which is used to link the implementation to the interface, not the service or bean id attribute.

I use $ mvn clean install to build and create my packages. When I try to deploy my EBA with Geronimo, though, I get this error message:

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.security required by bundle org.opentaps.notes.services.impl_2.0.1.SNAPSHOT cannot be resolved.

At first, this seemed to be a strange error, because my org.opentaps.notes.security is in the jar file of the org.opentaps.notes.services package:


$ jar tvf modules/notes/api/services/target/org.opentaps.notes.services.api-2.0.1-SNAPSHOT.jar
 ...
  1258 Wed Mar 07 16:29:08 PST 2012 org/opentaps/notes/security/NoteSecurity$Operation.class
   395 Wed Mar 07 16:29:08 PST 2012 org/opentaps/notes/security/NoteSecurity.class
 ...

and this package was being exported during $ mvn install:

[INFO] Copying artifact[org.opentaps, org.opentaps:notes.services.api:jar:2.0.1-SNAPSHOT, compile]

Then, looking more closely, there was a typo in modules/notes/api/services/pom.xml:

        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Export-Package>
                            org.opentaps.notes.services,
                            org.opentaps.notes.services.security
                        </Export-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>

org.opentaps.notes.services.security should have been org.opentaps.notes.security! Changing it fixed this bug.