i have changed the RegExp pattern register to this:

PackageResource.bind(application, ComponentInitializer.class,"progressbar.js");

in trunk and in the wicket_1_2 branch:
https://svn.sourceforge.net/svnroot/wicket/branches/WICKET_1_2/wicket-extensions

If you could test it it would be nice.

johan


On 5/20/06, Niclas Hedhman <[EMAIL PROTECTED]> wrote:

Gang,

Here at CodeDragons, we have been working the last days on getting the OSGi
support for Wicket totally right, and we have come quite a far bit on the
way, but not there yet. But I thought we should inform of progress and some
code examples of what actually works.

This work has been sponsored by ScanCoin, for which we are eternally grateful.

We know that at least two other parties are very keen on how to do this, so
let's look into the details. The source is available in the OPS4J subversion
repository at https://scm.ops4j.org/repos/ops4j/projects/pax/wicket/ and the
sub directories of service/ and samples/department-store. (Ignore the other
directories)

I will update the Pax Wicket documentation on the Wiki later today and/or
tomorrow...

- o - o - o -

The first and foremost goal was to allow for any Wicket component to be stuck
into any bundle and allow for dynamic load and unload of such resources,
without any significant compromises on the Wicket functionality.

The second goal was to make it easy to create re-usable components, that can
be deployed in their own bundles, and together with the first goal, upgraded
on-the-fly without taking down the servlet.


- o - o - o -

The important part regarding Wicket is that you can't construct the Wicket
component until the HTML and Java code matches. That means for each html tag
marked with a wicket:id, there must be a component at the right hierarchy
level of the Java object tree. So it was important to recognize that we need
a model of the content independent of the Wicket instantiation process.

Our solution centers around 2 interfaces and their implementations.

public interface Content
{
    String CONFIG_DESTINATIONID = "destinationId";
    String DESTINATIONID_UNKNOWN = "";

    String getDestinationId();
    Component createComponent();
}

public interface ContentContainer
{
    String CONFIG_CONTAINMENTID = "containmentId";
    String getContainmentID();
    List<Component> createComponents( String id );
    void dispose();
}


Essentially, the Content is the source and the ContentContainer is the sink
for components, and then the PageContent is the holder of the component tree.

Now, by leveraging the OSGi Service layer, we defined;
"A ContentContainer implementation must register itself as an OSGi service
and define its ContainmentID as a configuration property. The ContainmentID
must be unique within the OSGi framework."

"A Content implementation must register itself as an OSGi service and define
the DestinationID as a configuration property."

The DestionationID is an configuration property which tells the Pax Wicket
where the component should eventually be attached to. The DestionationID
consists of two parts, [containmentId].[wicketId].
The ContainmentID is the 'extension point' of the ContentContainer. All
Content that has a DestinationID containing the ContainmentID of a
ContentContainer will be attached at the WicketID of that ContentContainer.

Was that a bit dense??
Let's look at our example.

We define a Floor in a department store to have Franchisees. The layout of the
department store is sitting in a separate model bundle and is not part of
this explanation.

So we define a FloorPanel in the FloorView bundle;

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.
    }

}

And we register the floors in the 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();
        }
    }
}

What is important to notice is that we create the FloorContentContainer, sets
it ContainmentID (setting the DestinationID is all about inserting the Floor
in the right place of the building) and calls the register() method in the
superclass.

The DefaultContentContainer implements all the trickier bits to get this to
work, and we are trying to fulfill the second goal of making it relatievly
easy to create components.

Ok. So how about the Franchisees on each floor??

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 );
    }
}

And registering each Franchisee to their respective floor (by consulting the
model).

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();
    }
}

Again, create the Content (extending DefaultContent makes it easy), set the
DestinationID and call the register() method in the superlass.


And guess what? We are essentially done....

Ok, we need the FloorPanel and FranchiseePanel, which layouts everything to a
panel. In our simple example, we just do;

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 );
    }
}

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 );
        }
    }
}

The interesting bits sit in the

final List<Component> franchisees =
    container.createComponents( WICKET_ID_FRANCHISEE );

Here we are instructing the ContentContainer to create all its wicket children
of a particular WicketID. This in turn will trigger the container to call all
its children (Content) and if that Content is another ContentContainer it
will be chained all the way to the Content leaves.

Voila!! The FloorPanels are built (note that it can create many components
with the same ID). Then we add them into a ListView, which is standard Wicket
stuff and beyond the scope here.


Ok, so far so good... but where are the Pages, and more importantly the Wicket
Application, Servlets and all the other bits??

Well, first of all, we have not solved the "Page" issue yet, and so far the
sample only works with the default HomePage. The rest of the bits are all in
the Pax Wicket Service, i.e. nothing much you need to do. However, you need
to define a PaxWicketApplicationFactory and typically you do this in the
"application" bundle. The Activator for that looks something like;

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();
    }
}

The PaxWicketApplicationFactory needs the PageFactory and the Class of the
HomePage. It is expected more things will be needed when we start solving how
to support bookmarkable pages and such.
But essentially, the factory is plainly registered into the OSGi framework as
a service and the Pax Wicket Service will use the mount point, create a
servlet and deploy the Wicket application.

I expect we need another couple of days before the page part is completely
solved.

For completeness, I also include the OverviewPage and its support class;

public class OverviewPage extends WebPage
{
    public static final int FLOOR_PAGE_SIZE = 10;
    public 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 ) );
        }
    }
}

public class FloorTabPanel extends Panel
{

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


Ok, so there are still some outstanding issues;

1. Wicket-extensions is using a scanning of Jars/Zips approach to locate the
_javascript_ for the UploadProgressBar, which fails due to the OSGi frameworks
are not using standard URLs (zip/jar or file) for the resources, and hence
not scannable. I have suggested to Wicket that the location of the JS in
question is done with a direct reference, instead of the regexp. That should
work, but I am not sure if there are some additional thoughts why the regexp
has been chosen. We have had to disable the UploadProgressBar, as we use the
wicket-extensions in our sample.

2. Wicket has a MarkupCache which will hold on to various resources. If it
can't find it, it will store that information into the cache as well, so even
if the resource becomes available, it will not be served in a later request.
This can probably be solved simply by clearing the cache on each change of
the OSGi service registrations, and doesn't require any changes to Wicket.

3. We have not been able to fully understand the life cycle of a Page
instance in Wicket, and hope for some expert advice on the matter. Dynamic
changes to the component hierarchy will only occur when the page is created,
hence the need to understand this in detail.

4. There is an ambiguity in the OSGi specification of the HTTP service. Page
17-432 in chapter 102.2 it says;
<quote>
Therefore, the same Servlet instance must not be reused for registration with
another Http Service, nor can it be registered under multiple names. Unique
instances are required for each registration.
</quote>
It is unclear to us, whether one is allowed to unregister the servlet, and
then re-register the same servlet under a different mountpoint.
This ambiguity combined with a life cycle requirement in Wicket (which could
be reviewed and fixed) we have been forced to create a new Servlet whenever
the mountpoint is changed in PaxWicketApplicationFactory registration.
We consider this a somewhat acceptable limitation, and will not persue it
further.

5. Wicket supports the removal of components from containers, but we have no
experience in what one is allowed and not allowed to do and "when" such can
be done. As for now, we don't try to modify a created page in Wicket, but
asking for any advice in the matter from experienced Wicket developers.



Ok, this was a fairly long mail, and I hope some of you are actually
interested enough to read it all.



Cheers
Niclas


-------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Wicket-develop mailing list
Wicket-develop@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/wicket-develop

Reply via email to