Showing posts with label Wicket. Show all posts
Showing posts with label Wicket. Show all posts

Tuesday, April 5, 2011

Wicket: wicket-ajax Channel busy postponing

I got above error when using Wicket + wiQuery + wicket-push(cometd). I struggled for a day and finally, solved it.

In my case, the issue appeared on Firefox, but not happened on Chrome. Also When the page was first loaded, it worked fine. However, after reload the page, it happened. Any wicket's ajax request was blocked. And in the Wicket-Ajax-Debug window, "INFO: Channel busy postponing" message appeared.

The problem is caused by, **I guess**, wicket-ajax.js + jquery. When some jquery javascript start earlier than something, it will harm wicket-ajax.js. In my case, by cometd javascript, (see this) $.cometd.configure() and $.cometd.handshake() starts without waiting loading whole contents in the page. So I fix as follows.

$(window).load(function(){
    $.cometd.configure();
    $.cometd.handshake();
});

This will delay the two functions to start. And problem was gone.

I'm not sure this is a right way to do. Please leave comment if you find the better solution for the same problem.

Wednesday, February 16, 2011

Wicket push reload support

When I look the cometd's instruction page here, it seems the the based source code do not support reload. As shown in the example, it requires a client side javascript code. But it was not written anywhere, as far as I saw.

I start supporting reload extension on my custom wicketstuff-push (see the post). Let's see the code.

public abstract class CometdAbstractBehavior extends AbstractDefaultAjaxBehavior {
    private static final long serialVersionUID = 1L;
 
    // FIXME: put this in application scope, we may have several webapp using
    // CometdBehavior in the same web container!
    private final static String cometdServletPath = getCometdServletPath();
  
    private static final ResourceReference COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "org/cometd.js");  
    private static final ResourceReference JQ_JSON2 =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/json2.js");
    private static final ResourceReference JQ_COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cometd.js");

    private static final ResourceReference COMETD_RELOAD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "org/cometd/ReloadExtension.js");
    private static final ResourceReference JQ_COOKIE =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cookie.js");
    private static final ResourceReference JQ_COMETD_RELOAD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cometd-reload.js");

    @Override
    public void renderHead(final IHeaderResponse response) {
        super.renderHead(response);
        if (channelId == null) {
            throw new IllegalArgumentException("ChannelId in a CometdBehavior can not be null");
        }
        response.renderJavascriptReference(COMETD);
        response.renderJavascriptReference(JQ_JSON2);
        response.renderJavascriptReference(JQ_COMETD);

        response.renderJavascriptReference(COMETD_RELOAD);
        response.renderJavascriptReference(JQ_COOKIE);
        response.renderJavascriptReference(JQ_COMETD_RELOAD);
 
        response.renderJavascript(getInitCometdScript(), "initCometd");
        final String cometdInterceptorScript = getCometdInterceptorScript();
        if (cometdInterceptorScript != null) {
            response.renderJavascript(cometdInterceptorScript, "Interceptor"
                    + getBehaviorMarkupId());
        }
        response.renderJavascript(getSubscriberScript(), "Subscribe"
                + getBehaviorMarkupId());
    }

    protected final CharSequence getInitCometdScript() {
        return new PackagedTextTemplate(CometdBehavior.class, "CometdReloadInit.js").getString() +
            getConfigureCometdScript() + getHandshakeCometdScript(); 
    }

I add a javascript file. This code is written based on the reload example for jQuery (here).
/* handshake listener to report client IDs */
$.cometd.addListener("/meta/handshake", function(message)
{
    if (message.successful)
    {
        $('#previous').html(org.cometd.COOKIE.get('demoLastCometdID'));
        $('#current').html(message.clientId);
        org.cometd.COOKIE.set('demoLastCometdID', message.clientId, {
            'max-age': 300,
            path : '/',
            expires: new Date(new Date().getTime() + 300 * 1000)
        });
    }
    else
    {
        $('#previous').html('Handshake Failed');
        $('#current').html('Handshake Failed');
    }
});

/* Setup reload extension */
$(window).unload(function()
{
    $.cometd.reload();
});


Be care that this works on not only browser reloads, but also loading the same page with different parameters. It means, subscribing to the different channel also happens without handshake.

Saturday, February 12, 2011

Wicket push

Wicketstuff push has dramatically changed at 1.4.13. But the API changes so much at that version, and I cannot make it work. Since the 1.4.12 uses very OLD dojo libraries, I want to migrate to the new version.

While looking the release documents of Cometd 2.1.0, I find that it supports jQuery bindings and Dojo bindings. So, I try to change the bindings to jQuery based on 1.4.12.

The files I have changed are two, CometdAbstractBehavior.java, CometdBehavior.java. And import javascripts from cometd-javascript-jquery-2.1.0.war.

public abstract class CometdAbstractBehavior extends AbstractDefaultAjaxBehavior {
    private static final long serialVersionUID = 1L;

    // FIXME: put this in application scope, we may have several webapp using
    // CometdBehavior in the same web container!
    private final static String cometdServletPath = getCometdServletPath();
 
    private static final ResourceReference COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "org/cometd.js");  
    private static final ResourceReference JQ_JSON2 =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/json2.js");
    private static final ResourceReference JQ_COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cometd.js");

    @Override
    public void renderHead(final IHeaderResponse response) {
        super.renderHead(response);
        if (channelId == null) {
            throw new IllegalArgumentException("ChannelId in a CometdBehavior can not be null");
        }
        response.renderJavascriptReference(COMETD);
        response.renderJavascriptReference(JQ_JSON2);
        response.renderJavascriptReference(JQ_COMETD);

        response.renderJavascript(getInitCometdScript(), "initCometd");
        final String cometdInterceptorScript = getCometdInterceptorScript();
        if (cometdInterceptorScript != null) {
            response.renderJavascript(cometdInterceptorScript, "Interceptor"
                    + getBehaviorMarkupId());
        }
        response.renderJavascript(getSubscriberScript(), "Subscribe"
                + getBehaviorMarkupId());
    }

    protected final CharSequence getInitCometdScript() {
        return getConfigureCometdScript() + getHandshakeCometdScript(); 
    }

    protected final String getConfigureCometdScript() {
        return "$.cometd.configure('" + cometdServletPath + "')\n";
    }
 
    protected String getHandshakeCometdScript() {
        return "$.cometd.handshake()\n";
    }
 
    public final CharSequence getSubscriberScript() {
        return "$.cometd.subscribe('/" + getChannelId() + "', "
                + getPartialSubscriber() + ");\n";
    }

public class CometdBehavior extends CometdAbstractBehavior {

    @Override
    public final String getCometdInterceptorScript() {
  
        final Map map = new HashMap();
        map.put("behaviorMarkupId", getBehaviorMarkupId());
        map.put("url", getCallbackUrl().toString());
  
        return new PackagedTextTemplate(CometdBehavior.class, "CometdDefaultBehaviorTemplate.js").asString(map);
    }

    @Override
    public final CharSequence getPartialSubscriber() {
        return "onEventFor" + getBehaviorMarkupId();
    }

Yes, it works. Thanks for chrome's developer tool. It helps me lot! And I also refer this.

Friday, February 4, 2011

Wicket: NavigatorLabel for GridView

It seems that the NavigatorLabel do not support GridView. So I make it based on the Original NavigatorLabel. It's very simple.

/**
 * Label that provides Showing x to y of z message given for a DataTable. The message can be
 * overridden using the <code>NavigatorLabel</code> property key, the default message is used is of
 * the format <code>Showing ${from} to ${to} of ${of}</code>. The message can also be configured
 * pragmatically by setting it as the model object of the label.
 * 
 * @author Igor Vaynberg (ivaynberg)
 * 
 */
public class GridNavigatorLabel extends Label
{
    private static final long serialVersionUID = 1L;

    // TODO Factor this interface out and let dataview/datatable implement it
    private static interface PageableComponent extends IClusterable
    {
        /**
         * @return total number of rows across all pages
         */
        int getRowCount();

        /**
         * @return current page
         */
        int getCurrentPage();

        /**
         * @return rows per page
         */
        int getRowsPerPage();

        int getColumnsPerRow();
    }

    /**
     * @param id
     *            component id
     * @param table
     *            table
     */
    public GridNavigatorLabel(final String id, final GridView<?> table)
    {
        this(id, new PageableComponent()
        {

            /**
             * 
             */
            private static final long serialVersionUID = 1L;

            public int getCurrentPage()
            {
                return table.getCurrentPage();
            }

            public int getRowCount()
            {
                return table.getRowCount();
            }

            public int getRowsPerPage()
            {
                return table.getRows();
            }
   
            public int getColumnsPerRow()
            {
                return table.getColumns();
            }

        });

    }

    private GridNavigatorLabel(final String id, final PageableComponent table)
    {
        super(id);
        setDefaultModel(new StringResourceModel("NavigatorLabel", this,
            new Model<LabelModelObject>(new LabelModelObject(table)),
            "Showing ${from} to ${to} of ${of}"));
    }

    private class LabelModelObject implements IClusterable
    {
        private static final long serialVersionUID = 1L;
        private final PageableComponent table;

        /**
         * Construct.
         * 
         * @param table
         */
        public LabelModelObject(PageableComponent table)
        {
            this.table = table;
        }

        /**
         * @return "z" in "Showing x to y of z"
         */
        public int getOf()
        {
            return table.getRowCount();
        }

        /**
         * @return "x" in "Showing x to y of z"
         */
        public int getFrom()
        {
            if (getOf() == 0)
            {
                return 0;
            }
            return (table.getCurrentPage() * table.getRowsPerPage() * table.getColumnsPerRow()) + 1;
        }

        /**
         * @return "y" in "Showing x to y of z"
         */
        public int getTo()
        {
            if (getOf() == 0)
            {
                return 0;
            }
            return Math.min(getOf(), getFrom() + table.getRowsPerPage()* table.getColumnsPerRow() - 1);
        }

    }
}

Thursday, January 27, 2011

How to start JMS MessageListener latter than wicket application.

Issue

I'm using Spring for DI. I write as follows at applicationContext.xml for Wicket, Hibernate and JMS. The issue is caused by jmsListener start before wicketApplication.

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">  
  
    <!-- The wicket application bean -->
    <bean id="wicketApplication" class="my.project.MyWicketApplication" />

    <!-- specifies the place holder for resources of the project -->
    <bean id="placeholderConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreUnresolvablePlaceholders" value="false" />
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
        <property name="ignoreResourceNotFound" value="false" />
        <property name="locations">
            <list>
                <value>classpath*:/application.properties</value>
            </list>
        </property>
    </bean>

    <!-- JMS beans -->
    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616"/>
    </bean>
 
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
    </bean>
 
    <bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="ServerReceiveQueue"/>
    </bean>
 
    <bean id="jmsListener" class="my.project.jms.JmsListener"/>
  
    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="jmsListener"/>
    </bean>
  
    <!-- Data source, specifies the jdbc connection-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>${jdbc.driver}</value>
        </property>
        <property name="url">
            <value>${jdbc.url}</value>
        </property>
        <property name="username">
            <value>${jdbc.username}</value>
        </property>
        <property name="password">
            <value>${jdbc.password}</value>
        </property>
    </bean>

    <!-- Transaction manager -->
    <tx:annotation-driven transaction-manager="txManager" />
 
    <!-- setup transaction manager  -->
    <bean id="txManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>
 
    <!-- Hibernate session factory -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.connection.pool_size">5</prop>
                <prop key="hibernate.show_sql">false</prop>
            </props>
        </property>
          
        <property name="packagesToScan">
            <list>
                <value>my.project.domain</value>
            </list>
        </property>
    </bean>

    <context:component-scan base-package="my.project" />

</beans>

The detail of issues..

"jmsListener" is instanciated before PropertyPlaceholderConfigurer start. When jmsListener start it receives some messages. And register some information at another class which has @Service annotation. But, after wicket application start, *maybe* Spring DI by PropertyPlaceholderConfigurer also instanciate several beans which have @Service annotation. And two instance which is instanciated not by PropertyPlaceholderConfigurer and by PropertyPlaceholderConfigurer seems to have different name. That means, even if the data was stored in a static object, it is not shared among them. So the wicket application can not access the data which jmsListener receives.

Solution

I delay the start of jmsListener. Set autoStartup to "false" and call start() inside WicketApplication#init().

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="jmsListener"/>
        <property name="autoStartup" value="false"/>
    </bean>

public class MyWicketApplication extends WebApplication
{     
    @SpringBean
    private DefaultMessageListenerContainer jmsContainer;
 
    @Override
    protected void init() {
        super.init();
        InjectorHolder.getInjector().inject(this);
        jmsContainer.start();
    }
}

Are there more smarter ways to solve the issue?