OPS4J
  Pax Wicket
Added by Niclas Hedhman, last edited by Alin Dreghiciu on Jan 05, 2008  (view change)

Labels

 

This is the Pax Wicket wiki - for project documentation go to http://www.ops4j.org/projects/pax/wicket

The Pax Wicket Service is an OSGi service for supporting the creation of Wicket applications running on the OSGi platform.

The Pax Wicket Service allows you to load and unload Wicket pages and page content in runtime. It also allows you to register new Wicket Applications in runtime. All this without restarting the OSGi container.

Note: pax-wicket concepts are not equivalent to wicket concepts. Please be sure to clean the slate in your mind of all wicket concepts before reading about pax-wicket!

System Requirements

  1. An OSGi framework compliant with the R4 Core specification.
  2. A Config Admin Service that is R4 compliant.
  3. Pax Logging Service.
  4. Java5 virtual machine running on a supported operating system.

Building instructions

See the Building Instructions document.

Installation

See the Installation document

Pax Wicket Toolkit

We have now created a Toolkit subproject, which will contain widgets and other useful stuff when building Pax Wicket applications.

Tool Description
Menus A easy to use menu system.

Examples

There is an example of Pax Wicket running on top of Pax Web over at Pax Web Examples, follow the intructions for the Department Store example there.

Architecture

The basic architecture of the Pax Wicket eco system, is to make things smaller, runtime replaceable and a strict separation of concerns. We do that by leveraging the service layer in OSGi together with runtime wiring through the Config Admin service.

This will, as we shall see, allow us to break up the application in almost arbitrary bundles, and yet get a coherent view served by Wicket. One way to look at the architecture is the bundle break down. It looks like this;

+-----------------+  +------------+   +-------------+   +-----------+   +-----------+   +-------------+
| HTTP Service    |  | Pax Wicket |   |  Bus Logic  |   | Component |   |   Page    |   | Application |
| (Standard OSGi) |  |   Service  |   |  bundle(s)  |   | bundle(s) |   | bundle(s) |   |  bundle(s)  |
+-----      ------+  +---      ---+   +---      ----+   +--      ---+   +---      --+   +---      ----+
      \    /             \    /           \    /           \    /           \    /          \    /
+----- \  / ------------- \  / ----------- \  / ----------- \  / ----------- \  / ---------- \  / ----+
|     \ \/ /             \ \/ /           \ \/ /           \ \/ /           \ \/ /          \ \/ /    |
|      \  /               \  /             \  /             \  /             \  /            \  /     |
|       \/                 \/               \/               \/               \/              \/      |
|                                                                                                     |
|                                           OSGi Runtime Platform                                     |
+-----------------------------------------------------------------------------------------------------+

However, this is not very useful to get an understanding on how things really hang together. Let's start exploring the various aspects of the Pax Wicket Service architecture.

Content, ContentContainers and Components

First we need to introduce a couple of concepts.

  1. Content - Any Wicket component can be packaged as Content. [We should introduce wicket concepts later so we don't confuse the reader. Next sentence is good!] "Content" is an OSGi service [that implements what or does what??] that gets registered into the OSGi framework. Content bundles are written by the developer, but there is extensive support provided by the Pax Wicket Service to make this task really simple.
  2. Destination - Each Content service must in runtime define a Destination. The Destination is described in a DestinationID which consists of two parts. The first part is which Containment point should the Content be bound to, and the second part of the DestinationID is the wicket:id within that Containment. [Note: based on my subsequent conversations with Niclas, I find the term "destination" to be confusing... According to Niclas:  "containmentID == placeholder where content is added; destinationID == identifier of such content.". Can't we call this "ContentId" or something instead? In my mind, if the container is the placeholder, then it is the destination...]
  3. Containment - Containment is an extension point to which Content can be attached. Each Containment may have many wicket:ids to be populated. [Here, we're mixing up wicket concepts with pax-wicket concepts... this is one source of confusion...] The Containment is identified by a ContainmentID which must be unique within the OSGi runtime platform. [I have problems with the term "containment", which is defined as "the act or condition of containing". Try substituting that in the last sentence: "The act or condition of containing is an extension point..."]
  4. A "ContentContainer" is an OSGi service that contains Content. Each ContentContainer gets registered with a ContainmentID and implements the ContentContainer interface. ContentContainers look for DestinationIDs that have their first part equal to the ContentContainer's ContainmentID, i.e. <destinationID> ::= <containmentID> "." <wicketID>.

[These definitions should make things clearer... I think we need to work on them some more. We should write more about the IDs here, too, since they will help to understand the examples below.] 

So, let's look at the primary interfaces of Content and ContentContainer.

package org.ops4j.pax.wicket.service;

import wicket.Component;

public interface Content
{
    String DESTINATIONID = "destinationId";
    // What is this for? Maybe add a comment...
    String DESTINATIONID_UNKNOWN = "";
    // Maybe add a comment to explain this a bit...
    String getDestinationId();
    // We probably don't need a comment here... ;-)
    Component createComponent();

}
package org.ops4j.pax.wicket.service;

import wicket.Component;
import java.util.List;

public interface ContentContainer
{
    String CONTAINMENTID = "containmentId";
    // Man... I just can't get the word "containment" into my head... :-(
    String getContainmentID();
    // This probably deserves a comment...
    List<Component> createComponents( String id );

    void dispose();
}

As you see, there is not much in these interfaces, and that can seem very strange at first. The issue is that quite a lot of the semantics of the Content and ContentContainer are in the behavior and interaction with the OSGi framework, which is not expressed in the interfaces.

There are only a few "rules" you need to follow when using Pax Wicket:

  1. Content must be registered as OSGi service, and must have a unique org.osgi.framework.Constants.SERVICE_PID set. [Can we explain why this is so?]
  2. ContentContainer must listen for Content service registrations in the OSGi framework (see example below).
  3. ContentContainer must keep track of which Content service have a DestionationID that matches its own ContainmentID.
  4. ContentContainer must delegate the createComponents() method to the Content that is wired to each of the wicket:ids. [If we mention this here, we'll need to explain more about the wicket:ids above. I think we'll need a full section that explains how wicket is related to pax-wicket. This is not clear.]

In most cases, the above should be fairly standard. Pax Wicket therefore provides two default implementations of these interfaces, which should be sufficient in most cases:

  • DefaultContent
  • DefaultContentContainer

By using these default implementations, there is very little overhead that the developer needs to deal with (see Writing Content services and Writing ContentContainer services below).

Pages

Need to explain that a pax-wicket page is not equivalent to a wicket page. Also, how is "page" related to "application" and "content"? Describe also the difference between the page _class_ and instantiations of that class, since wicket seems to drive the concept of class=implementation for a page into the developer's head.  Whens should "page" be used? How should it be used? .....

Pax Wicket Applications

Pax Wicket Service supports many Pax Wicket applications being deployed simultaneously onto the same instance of Pax Wicket Service. For each Pax Wicket application, a separate Servlet will be created and mounted on to a configurable mount point in the URL space. However, care must be taken to ensure that all ContainmentIDs are registered under unique names.

[Give an explanation about the appropriate use of a pax-wicket application] 

Business Logic

Pax Wicket adheres to the principle of "separation of concerns" by leaving the concern of the business logic to some other service. The Pax Wicket Service should only be used to generate a view of application, and should strictly avoid any interference with the business logic. In other words, the view should only look up OSGi services so it can obtain data to present a view of the data.

By harnessing the modularity of OSGi, we get a very smooth separation of the web tier and the business logic. It is not within the scope of this document to describe how to model business logic on the OSGi platform.

Writing Content services

Let's look at the code from the department-store sample in the OPS4J svn repository.

We need an Activator;

public class Activator
    implements BundleActivator
{
    private List<ServiceRegistration> m_registrations;

    public Activator()
    {
        m_registrations = new ArrayList<ServiceRegistration>();
    }

    public void start( BundleContext bundleContext )
        throws Exception
    {
        String depStore = DepartmentStore.class.getName();
        ServiceReference depStoreService = bundleContext.getServiceReference( depStore );
        DepartmentStore departmentStore = (DepartmentStore) bundleContext.getService( depStoreService );

        m_registrations = new ArrayList<ServiceRegistration>();
        List<Floor> floors = departmentStore.getFloors();
        for( Floor floor: floors )
        {
            List<Franchisee> franchisees = floor.getFranchisees();
            for( Franchisee franchisee : franchisees )
            {
                String destinationId = floor.getName() + ".franchisee";
                FranchiseeContent content = new FranchiseeContent( bundleContext, franchisee  );
                content.setDestinationId( destinationId );
                ServiceRegistration registration = content.register();
                m_registrations.add( registration );
            }
        }
    }

    public void stop( BundleContext bundleContext )
        throws Exception
    {
        for( ServiceRegistration registeration : m_registrations )
        {
            registeration.unregister();
        }
        m_registrations.clear();
    }
}

In this sample, the business logic sits in a separate department store model bundle, and the component knows how to extract the available Franchisees, and which floor they are located on. A more generic approach is to set the DestinationID to Content.DESTINATIONID_UNKNOWN and let a separate bundle figure out the wiring. We will probably come up with various strategies around this in the future.

We need to create a Content class, which we subclass from the DefaultContent to save us a lot of work. For the Franchisee it looks like;

public class FranchiseeContent extends DefaultContent
{
    private Franchisee m_franchisee;

    public FranchiseeContent( BundleContext context, Franchisee franchisee )
    {
        super( context, franchisee.getName()  );
        m_franchisee = franchisee;
    }

    protected Component createComponent( String id )
    {
        return new FranchiseePanel( id, m_franchisee );
    }
}

Nothing much to do here, just instantiate the Wicket component on demand.

Next, we need the Wicket part of our Content.

public class FranchiseePanel extends Panel
    implements Serializable
{
    private static final long serialVersionUID = 1L;

    private static final String WICKET_ID_NAME_LABEL = "name";
    private static final String WICKET_ID_DESC_LABEL = "description";

    public FranchiseePanel( String id, Franchisee franchisee )
    {
        super( id );

        Label nameLabel = new Label( WICKET_ID_NAME_LABEL, franchisee.getName() );
        add( nameLabel );

        Label descLabel = new Label( WICKET_ID_DESC_LABEL, franchisee.getDescription() );
        add( descLabel );
    }
}

This is standard Wicket stuff. It is provided a Franchisee from the business logic model, and adds two Wicket Labels. This must correspond to an HTML snippet;

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en" lang="en">
<body>
    <wicket:panel>
        Name: <span wicket:id="name"></span>

        <br/>
        Description: <span wicket:id="description"></span>

        <br/>
    </wicket:panel>
</body>
</html>

For those who are not vivid Wicket users (like myself), it is important that the <span> element contains an opening and closing tag, instead of the common <span ... /> format. I am not sure why Wicket makes this distinction.

Bundle Manifest

We need a bundle manifest for the above code, which needs to look like this;

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: niclas
Build-Jdk: 1.5.0_06
Extension-Name: departmentstore.view.franchisee
Specification-Vendor: OPS4J - Open Participation Software for Java
Implementation-Vendor: OPS4J - Open Participation Software for Java
Implementation-Title: departmentstore.view.franchisee
Implementation-Version: 0.1.0.SNAPSHOT
Bundle-Activator: org.ops4j.pax.wicket.samples.departmentstore.view.fr
 anchisee.internal.Activator
Bundle-Version: 0.1.0.SNAPSHOT
Bundle-Vendor: OPS4J - Open Participation Software for Java
Import-Package: org.ops4j.pax.wicket.samples.departmentstore.model;ver
 sion=0.1.0.SNAPSHOT,org.ops4j.pax.wicket.service;version=0.1.0.SNAPSH
 OT,org.osgi.framework;version=1.2,wicket;version=1.2,wicket.markup.ht
 ml.basic;version=1.2,wicket.markup.html.panel;version=1.2
Bundle-Name: OPS4J - Pax Wicket Department Store - View - Franchisee
Bundle-Description: Pax Department Store Franchisee View.
Bundle-SymbolicName: departmentstore.view.franchisee

The manifest is built by Maven and the maven-osgi-plugin. The pom.xml required for this bundle can be found here.

The important parts to notice are;

  • No exports !
  • Import the Pax Wicket Service package.
  • Import the used Wicket packages.
  • Import the OSGi framework.
  • If you need logging, import Pax Logging.

Conclusion

In essence, this concludes what we need to know about writing Content services. We have seen that it is not very hard, and you will see that most of your issues will be ordinary Wicket stuff, i.e. getting the hierarchies right, which is not related to Pax Wicket Service at all.

Writing ContentContainer services

ContentContainers are aggregation of Content which are typically the re-registered as a larger Content service. For each wicket:id, we need one or more Content, and without it Wicket will fail.

That said, let's look at our Activator

public class Activator
    implements BundleActivator
{

    private List<FloorContentContainer> m_containers;
    private List<ServiceRegistration> m_registrations;

    public Activator()
    {
        m_containers = new ArrayList<FloorContentContainer>();
        m_registrations = new ArrayList<ServiceRegistration>();
    }

    public void start( BundleContext bundleContext )
        throws Exception
    {
        String depStoreServiceName = DepartmentStore.class.getName();
        ServiceReference depStoreServiceReference = bundleContext.getServiceReference( depStoreServiceName );
        DepartmentStore departmentStore = (DepartmentStore) bundleContext.getService( depStoreServiceReference );
        List<Floor> floors = departmentStore.getFloors();

        String destinationId = "swp.floor";
        for( Floor floor : floors )
        {
            FloorContentContainer container =
                new FloorContentContainer( floor, floor.getName(), destinationId, bundleContext );
            m_containers.add( container );
            container.setDestinationId( destinationId );
            container.setContainmentId( floor.getName() );
            ServiceRegistration registration = container.register();
            m_registrations.add( registration );
        }
    }

    public void stop( BundleContext bundleContext )
        throws Exception
    {
        for( ServiceRegistration registration : m_registrations )
        {
            registration.unregister();
        }
        m_registrations.clear();
        for( ContentContainer floor : m_containers )
        {
            floor.dispose();
        }
    }
}

We obtain the Floor instances from our business logic model, and for each floor we create a FloorContentContainer that will look for Content with the DestinationID containing the Floor ContainmentID. The DefaultContentContainer, which we subclass for the FloorContentContainer, also implements the Content interface, so we need to set the DestinationID of the Floor itself. In this case we have hardcoded the DestinationID to "swp.floor", meaning we want each floor to be attached to the Containment "swp" at a wicket:id="floor".

public class FloorContentContainer extends DefaultContentContainer
{
    private final Floor m_floor;

    public FloorContentContainer( Floor floor, String containmentId, String destinationId,
                                  BundleContext bundleContext )
    {
        super( containmentId, destinationId, bundleContext );
        m_floor  = floor;
    }

    protected Component createComponent( String id )
    {
        return new FloorPanel( id, this, m_floor );
    }

    protected void removeComponent( Component component )
    {
        //TODO: Auto-generated, need attention.
    }

}

The FloorContentContainer need to implement the createComponent( String id ) abstract method, needed by the superclass. The removeComponent( Component comp ) method has not been worked out at writing time.

The above simply generates the FloorPanel, which looks like this;

public class FloorPanel extends Panel
{

    public static final String WICKET_ID_NAME_LABEL = "name";
    private static final String WICKET_ID_FRANCHISEE = "franchisee";
    private static final String WICKET_ID_FRANCHISEES = "franchisees";

    public FloorPanel( String id, ContentContainer container, Floor floor )
    {
        super( id, new Model( floor.getName() ) );
        Label nameLabel = new Label( WICKET_ID_NAME_LABEL, floor.getName() );
        add( nameLabel );
        final List<Component> franchisees = container.createComponents( WICKET_ID_FRANCHISEE );
        if( franchisees.isEmpty() )
        {
            Panel p = new Panel( "franchisees" );
            p.add( new Label( "franchisee", "No Franchisees on this floor." ) );
            add( p );
        }
        else
        {
            ListView listView = new ListView( WICKET_ID_FRANCHISEES, franchisees )
            {
                protected void populateItem( final ListItem item )
                {
                    item.add( (Component) item.getModelObject() );
                }
            };
            add( listView );
        }
    }

}

First we add a Wicket Label with the name of the Floor. The FloorContentContainer passes itself to the FloorPanel, so that it can request the creation of the Wicket Component(s) of a particular wicket:id. We then make sure that we handle that we have no Franchisees on this Floor. And if we do, then we add them all into a ListView, which is rendered below as a html table.

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en" lang="en">
<body>
    <wicket:panel>
        <b>Floor: <span wicket:id="name"/></b>
        <br/>
        <table>
            <tr wicket:id="franchisees" ><td><span wicket:id="franchisee"></span></td></tr>
        </table>
    </wicket:panel>
</body>
</html>

In the html, we once again uses Panel, but could in fact be any Wicket Component, including compouded and extended ones.

Bundle Manifest

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: niclas
Build-Jdk: 1.5.0_06
Extension-Name: departmentstore.view.floor
Specification-Vendor: OPS4J - Open Participation Software for Java
Implementation-Vendor: OPS4J - Open Participation Software for Java
Implementation-Title: departmentstore.view.floor
Implementation-Version: 0.1.0.SNAPSHOT
Bundle-Activator: org.ops4j.pax.wicket.samples.departmentstore.view.fl
 oor.internal.Activator
Bundle-Version: 0.1.0.SNAPSHOT
Bundle-Vendor: OPS4J - Open Participation Software for Java
Import-Package: org.ops4j.pax.wicket.samples.departmentstore.model;ver
 sion=0.1.0.SNAPSHOT,org.ops4j.pax.wicket.service;version=0.1.0.SNAPSH
 OT,org.osgi.framework;version=1.3,wicket;version=1.2,wicket.model;ver
 sion=1.2,wicket.markup.html.basic;version=1.2,wicket.markup.html.list
 ;version=1.2,wicket.markup.html.panel;version=1.2
Bundle-Name: OPS4J - Pax Wicket Department Store - View - Floor
Bundle-Description: Pax Department Store Floor View.
Bundle-SymbolicName: departmentstore.view.floor

The manifest is built by Maven and the maven-osgi-plugin. The pom.xml required for this bundle can be found here.

Again, there are NO exports, and we import the same stuff as in the Content case, i.e. OSGi framework, Pax Logging if needed, Pax Wicket Service and the relevant Wicket packages being used.

Conclusion

As you have seen, it is not very difficult to write the ContentContainer if you subclass the DefaultContentContainer.

Writing Application services

The above sample wouldn't do anything unless we tie it into a Page. At the time of writing, we are not completely done with Page support, so the sample only utilizes the HomePage concept in Wicket, but uses AjaxTabbedPanel to show different floors.

The Activator needs to initialize the Pax Wicket application, and get everything running. Fortunately, that is very simple (as well).

public class Activator
    implements BundleActivator
{
    private ContentContainer m_store;
    private ServiceRegistration m_serviceRegistration;

    public void start( BundleContext bundleContext )
        throws Exception
    {
        IPageFactory factory = new IPageFactory()
        {
            public Page newPage( final Class pageClass )
            {
                return new OverviewPage( m_store, "Sungei Wang Plaza" );
            }

            public Page newPage( final Class pageClass, final PageParameters parameters )
            {
                return new OverviewPage( m_store, "Sungei Wang Plaza" );
            }
        };
        m_store = new DefaultPageContainer( "swp", bundleContext, factory );
        Properties props = new Properties();
        props.put( PaxWicketApplicationFactory.MOUNTPOINT, "swp" );
        PaxWicketApplicationFactory applicationFactory = new PaxWicketApplicationFactory( factory, OverviewPage.class );
        String serviceName = PaxWicketApplicationFactory.class.getName();
        m_serviceRegistration = bundleContext.registerService( serviceName, applicationFactory, props );
    }

    public void stop( BundleContext bundleContext )
        throws Exception
    {
        m_serviceRegistration.unregister();
        m_store.dispose();
    }
}

We need to provide a Wicket IPagefactory, since out HomePage does not have a default constructor. We create a DefaultPageContent, which is a ContentContainer that itself is not a Content (at least not yet). The DefaultPageContent is passed to the OverviewPage, via the newPage() methods in the IPageFactory, which IMHO is not the cleanest, since we now have an awkward cyclic dependency. This will be further refined when we solve the whole Page management system in the Pax Wicket Service.

We then create and register the PaxWicketApplicationFactory under a URL mountpoint of "swp".

The OverviewPage uses AjaxTabbedPanels and need a little bit of support from a class we call FloorTabPanel.

public class FloorTabPanel extends Panel
{

    public FloorTabPanel( String id )
    {
        super( id );
    }
}

Not much about this class, other than it serving as the locator for the html needed.

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en" lang="en">
<body>
    <wicket:panel>
        <div wicket:id="floor"></div>
    </wicket:panel>
</body>
</html>

There is the wicket:id to which the FloorContent will bind themselves. The reason for the FloorTabPanel is a bit obscure, but related to the hierarchy that Wicket creates around the AjaxTabbedPanel. In effect, the Panel one is requested to create must have the name "panel" which sits inside the html for the TabbedPanel component, and we are a content of that wicket:id. We could possibly get away with having DestinationIDs set to "panel" for the Floor (instead of "swp.floor" use "swp.panel", but we thought that would be harder to follow). Well, I have tried to explain it...

The OverviewPage is then the last piece needed.

public class OverviewPage extends WebPage
{
    private static final String WICKET_ID_LABEL = "storeName";

    public OverviewPage( ContentContainer container, String storeName )
    {
        Label label = new Label( WICKET_ID_LABEL, storeName );
        add( label );
        final List<Component> floors = container.createComponents( "floor" );
        List tabs = new ArrayList();
        for( final Component floor : floors )
        {
            String tabName = (String) floor.getModelObject();
            tabs.add( new AbstractTab( new Model( tabName ) )
            {
                public Panel getPanel( String panelId )
                {
                    Panel panel = new FloorTabPanel( panelId );
                    panel.add( floor );
                    return panel;
                }
            }
            );
        }
        if( tabs.isEmpty() )
        {
            add( new Label( "floors", "No Floors installed yet." ) );
        }
        else
        {
            add( new AjaxTabbedPanel( "floors", tabs ) );
        }
    }
}

We instantiate a Wicket Label to display the name of the department store. And again, we are passed the ContentContainer in the constructor (in this case a DefaultPageContent instance), which we use to create the Wicket Components needed for the Page (and as explained earlier, the createComponents() method will delegate down the ContentContainer chain to the leaves).

We have earlier stored the Floor name in the Model of the Floor, to avoid a dependency. That Floor name is used as the title of the Tab, and we just follow the Wicket pattern to create tabs.

We then check if there were any floors, if not put a simple text message about that, otherwise instantiate a AjaxTabbedPanel named "floors" to be injected into our html (seen below).

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en" lang="en">
<head>
    <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
    <span wicket:id="storeName">StoreName goes here</span>
    <p>
        <div wicket:id="floors" class="floorpanel" ></div>
    </p>
</body>
</html>

Bundle Manifest

There is nothing much different in this bundle manifest from the others.

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: niclas
Build-Jdk: 1.5.0_06
Extension-Name: departmentstore.view.app
Specification-Vendor: OPS4J - Open Participation Software for Java
Implementation-Vendor: OPS4J - Open Participation Software for Java
Implementation-Title: departmentstore.view.app
Implementation-Version: 0.1.0.SNAPSHOT
Bundle-Activator: org.ops4j.pax.wicket.samples.departmentstore.view.in
 ternal.Activator
Bundle-Version: 0.1.0.SNAPSHOT
Bundle-Vendor: OPS4J - Open Participation Software for Java
Import-Package: org.ops4j.pax.wicket.samples.departmentstore.model;ver
 sion=0.1.0.SNAPSHOT,org.ops4j.pax.wicket.service;version=0.1.0.SNAPSH
 OT,org.osgi.framework;version=1.3,wicket;version=1.2,wicket.extension
 s.ajax.markup.html.tabs;version=1.2,wicket.extensions.markup.html.tab
 s;version=1.2,wicket.markup.html;version=1.2,wicket.markup.html.basic
 ;version=1.2,wicket.markup.html.panel;version=1.2,wicket.model;versio
 n=1.2,wicket.protocol.http;version=1.2,wicket.settings;version=1.2
Bundle-Name: OPS4J - Pax Wicket Department Store - View - App
Bundle-Description: Pax Department Store View Application.
Bundle-SymbolicName: departmentstore.view.app

The manifest is built by Maven and the maven-osgi-plugin. The pom.xml required for this bundle can be found here.

Again, there are NO exports, and we import the same stuff as in the Content case, i.e. OSGi framework, Pax Logging if needed, Pax Wicket Service and the relevant Wicket packages being used.

Conclusion

FIXME Need to update when we know more about the Page management.

Running Embedded inside another Servlet container

This is an area that is yet to be explored, but we could learn the basic principles from the RSP-UI project and the Http Service Bridge, alternatively from Simon Kaegi's effort in Equinox. FIXME Name and URL