Archive

Posts Tagged ‘wicket’

Wicket: fournir des données JSON via Ajax

septembre 3, 2010 1 commentaire

[Article also available in English]

Il y a quelques semaines, nous devions retourner des données sous forme JSON suite à des appels JavaScript. N’étant pas familier avec la chose, nous étions partis sur une page ne retournant qu’un contenu JSON. Quelque chose comme ça:

 @Override
    protected void onRender(final MarkupStream markupStream)
    {
        try
        {
            OutputStream outputStream = getResponse().getOutputStream();
            outputStream.write(jsonData.getBytes());
            outputStream.flush();
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }

    }

L’url de cette page était fournie au code JavaScript via cet appel:

 
RequestCycle.get().urlFor(jsonPage);

Bien évidemment, l’appel et la gestion de cette page étaient encapsulés, histoire de ne pas polluer chaque utilisateur avec ces subtilités.
Toutefois, bien que fonctionnelle, cette solution n’est pas parfaite. En effet, cette page supplémentaire implique un contexte différente où les infos doivent être amenées. Rester au sein même du composant/contexte pourrait être plus plus sympa (c’est certes toujours possible, mais bien moins propre et requérant plus de travail). Cette solution est également plutôt verbeuse.

Le bon côté de la chose est que cette page, avec peu de contenu, prend également peu de place une fois sérialisée, ce qui arrive souvent avec Wicket. Je vous l’disais, rien n’est parfait ;)

Cependant, plus récemment, je cherchais une façon de coordonner un Behavior Ajax Wicket avec une fonction jQuery, les deux se déclenchant sur le même événement JavaScript. En chemin, j’ai trouvé cette "astuce" de Richard Wilkinson dans une conversation nommée Wicket, Ajax and JSON. Et devinez quoi: c’est tout simple de faire interagir du JavaScript et un Behavior via des données JSON (ou quelque contenu de votre choix).
Voici le code de Richard:

 
    AbstractAjaxBehavior behaviour = new AbstractAjaxBehavior()
        {
            private static final long serialVersionUID = 1L;

            @SuppressWarnings("unchecked")
            public void onRequest()
            {
                //get parameters
                final RequestCycle requestCycle = RequestCycle.get();

                final PageParameters pageParameters = new PageParameters(requestCycle.getRequest().getParameterMap());
               
                //do something using nice json library to produce a string of json
                String json = .......
               
               
                requestCycle.setRequestTarget(new StringRequestTarget("application/json", "utf-8", json));
            }

        };
        add(behaviour);

Pour accéder à l’url de ce Behavior, il suffit de procéder ainsi:

 behaviour.getCallbackUrl(true)

Ma première implémentation tourne nickel, même si je ne suis pas sûr de toutes les implications du booléen fourni pour obtenir l’url, dont la JavaDoc est:
* @param onlyTargetActivePage
* if true the callback to this behavior will be ignore if the page is not the last
* one the user accessed

Quoiqu’il en soit, au final, l’essentiel est dans la facilité avec laquelle tout cela est faisable. Finies la page et les lignes de code supplémentaires, ainsi que de passer des infos à travers tout ce petit monde. Ce Behavior résout tout cela joliment.

Bien sûr, l’intégration de JavaScript avec le côté serveur est toujours un travail conséquent, mais au moins Wicket fournit vraiment des outils efficaces pour cela !

:)

++
joseph

PS: dans sa réponse, Richard parle également de l’intégration de jQuery dans Wicket, via le projet anciennement nommé wiQuery et récemment intégré dans wicketstuff-core en tant que "jquery" project. Quelqu’un l’a t il déjà utilisé ? Des retours d’expérience ?
PS2: Plus d’info sur Richard sur son blog Out for a duck, all about Wicket, sans nouvelle entrée depuis quelque temps malheureusement.

Tags:, , ,

Wicket: providing JSON content through Ajax

septembre 2, 2010 5 commentaires

[Article également disponible en Français.]

A few weeks ago, we had a need to give back some JSON content to some client side JavaScript. Not knowing exactly how to do that, we went for a page rendering itself with only the JSON content. Something like that:

 @Override
    protected void onRender(final MarkupStream markupStream)
    {
        try
        {
            OutputStream outputStream = getResponse().getOutputStream();
            outputStream.write(jsonData.getBytes());
            outputStream.flush();
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }

    }

Then, the url for this page was provided to the page through

 
RequestCycle.get().urlFor(jsonPage);

For sure, the call to this page was done through some component encapsulating the whole business.
Yet, while working, this solution had drawbacks. The biggest one, IMHO, being that the extra page means a different context, where the info have to be pushed. It would be nice to be able to stay in the component/context (which is still doable, I agree, but less clean and more work). It’s also quite verbose. On the good side on the picture, this page being filled with less content is then small on the disk when about to be serialized way and forth. You can’t have the cake and eat it ;)

Recently, I was looking for a way to deal with a Wicket Ajax behavior and some jQuery function both acting on the same JavaScript event. And on the way, I found this answer from Richard Wilkinson on a topic named Wicket, Ajax and JSON. And guess what, it’s dead simple to interact between JavaScript and a behavior through JSON data (or whatever content you prefer).
Here is the code from Richard:

 
    AbstractAjaxBehavior behaviour = new AbstractAjaxBehavior()
        {
            private static final long serialVersionUID = 1L;

            @SuppressWarnings("unchecked")
            public void onRequest()
            {
                //get parameters
                final RequestCycle requestCycle = RequestCycle.get();

                final PageParameters pageParameters = new PageParameters(requestCycle.getRequest().getParameterMap());
               
                //do something using nice json library to produce a string of json
                String json = .......
               
               
                requestCycle.setRequestTarget(new StringRequestTarget("application/json", "utf-8", json));
            }

        };
        add(behaviour);

Then, to get at the behavior, one just has to provide the JavaScript with the outcome of

 behaviour.getCallbackUrl(true)

The basic implementation I did and used work all fine, yet I’m not sure yet of all the implications of the boolean provided, whose javadoc is:
* @param onlyTargetActivePage
* if true the callback to this behavior will be ignore if the page is not the last
* one the user accessed

Anyway, in the end, what matters is how easy it is to do ! No more extra page, extra lines and information being passed around. This simple behavior does it all nicely !

Yes, integrating JavaScript and server side is still some work, but damn Wicket provides really first level tools for this!

:)

++
joseph

PS: Richard speaks as well of JQuery integration for Wicket, formerly wiquery and now integrated into wicketstuff-core as the jquery project. Did anyone ever try it ? Any feedback ?
PS2: More about Richard on his blog Out for a duck, all about Wicket (no new entry since a while though).

Tags:, , ,

Small Wicket tips

juillet 23, 2010 Poster un commentaire

[Cet article est également disponible en français.]
In the last weeks, I’ve been doing quite some Wicket work.

Here are some small tips gathered on the way :)

  • If you use an AjaxButton, don’t forget to override onError and to add a feedback panel to the target! Indeed, an AjaxButton triggers form validation. And if this fails, then feedback messages should have been created on the way. Having them displayed is kind of handy ;) .
  • Know and use Component.setRenderBodyOnly(boolean renderTag).This little method allows to skip the component tag when rendering. Doesn’t sound crazy like that, but comes handy when one has an optional fieldset.
  • FormComponent.updateModel() is only effective if the new model object isn’t equal to the previous one.Indeed, FormComponent.updateModel() calls Component.setDefaultModelObject(), when checks for non equality before setting the new object. Why ? It saves making an extra page version if no change was done. Yet, if your equals isn’t appropriate, this may result in changes not being done for no visible reason. To avoid this, override Component.getModelComparator() and provide an appropriate comparator, like one testing only for same reference equality.

Hopefully it may help someone :)

++
joseph

Tags:, ,

Petites astuces Wicket

juillet 23, 2010 Poster un commentaire

[This article is also available in English.]
Ces dernières semaines, j’ai pas mal utilisé Wicket.

Voici quelques petites astuces rencontrées en chemin :)

  • Si vous utilisez un AjaxButton, surchargez onError et ajoutez un FeedbackPanel à ce qui sera réaffiché! En effet, un AjaxButton déclenche la validation du formulaire auquel il est rattaché. Et si cette validation échoue, il est fort probable que des FeedbackMessage soient créés en chemin. Il serait dommage de ne pas les afficher ;) .
  • Connaissez et utilisez Component.setRenderBodyOnly(boolean renderTag).Cette petit méthode permet de ne pas afficher le tag html auquel le composant est attaché, tout en affichant son contenu. Ca peut paraitre anodin, mais cela s’avère bien pratique lorsqu’on veut afficher de façon optionnel le fieldset entourant un composant.
  • FormComponent.updateModel() n’est effectif que si le nouvel objet rattaché au modèle n’est pas égal au précédent.En effet, FormComponent.updateModel() appelle Component.setDefaultModelObject() avec le nouvel objet. setDefaultModelObject vérifie alors que le nouvel objet ne soit pas égal au précédent. Il fait cela afin d’éviter de créer une version non nécessaire de la page (qui sera ensuite gardée dans la PageMap). Ceci dit, si votre equals() n’est pas approprié, cela peut amener certains changements à ne pas être pris en compte (sans raison évidente). Pour éviter cela, il suffit de surcharger Component.getModelComparator() et de fournir un comparateur correspondant. Par exemple un comparateur ne vérifiant que l’égalité des références.

J’espère que cela pourra aider !

++
joseph

Tags:, ,

Wicket et ses fonctionnalités de templating

juin 19, 2010 1 commentaire

[This note is also available in English.]

Wicket fournit des fonctionnalités de templating. Rien de bien folichon, mais c’est tout de même bien pratique, surtout quand il faut intégrer du JavaScript.

Ces fonctionnalités sont cependant très peu discutées voir même, je suspecte, globalement inconnues. Aussi, creusons un peu la chose!

Comme tout système de templating, tout tourne autour de texte contenant des variables, définies par ${variable} et dont les valeurs sont données via Java.

Voyons un exemple simple, un fichier de template nommé javascript.tpl:

alert('${variable}');

Wicket permet d’aisément accéder à ces templates en tant que ressources mises dans des packages Java, via la classe PackagedTextTemplate:

public PackagedTextTemplate(final Class clazz, final String fileName)

Par exemple:

PackagedTextTemplate jsTemplate = new PackagedTextTemplate(this.getClass(), "javascript.tpl");

Ainsi, le template se trouve à côté de la classe Java et de l’html l’utilisant, rendant le tout aisé d’utilisation.

Les variables sont fournies au moyen d’une simple Map:

Map parameters = new HashMap();
parameters.put("variable","test working");

Au final ce template trônera certainement au milieu d’html. Aussi Wicket fournit plusieurs options pour cela:

  • contribution au header html:
    add(TextTemplateHeaderContributor.forJavaScript(jsTemplate, new  Model((Serializable) parameters)));
  • accolé à des éléments du body html:
    Java:

    Label myScript = new Label("myScript", new JavaScriptTemplate(jsTemplate).asString(parameters));
    myScript.setEscapeModelStrings(false);
    add(myScript);

    Html :

    <wicket:container wicket:id="myScript"></wicket:container>

Vous avez sans doute remarqué que, dans chacun des cas, je ne fournis pas le tag qui devrait entourer le tout. Pas d’inquiétude à avoir, Wicket le fait pour vous.
L’html résultant est en effet:

<script type="text/javascript"><!--/*--><![CDATA[/*><!--*/
alert('test working');
/*-->]]>*/</script>

Si le template contenait plutôt du CSS, il faut juste utiliser un CssTemplate au lieu du JavaScriptTemplate.

Quelques info supplémentaires sont disponibles sur le wiki wicket: Including CSS resources.

++
joseph

(Petite) Astuce wicket : ne pas caster sa classe Application

Salut

Fréquemment, il m’arrive du voir du code wicket tel que

((MyApplication)Application.get()).myApplicationSpecificMethod();

Perso, cela me pique toujours un peu les yeux. Or, il y a tout simple, à savoir définir dans MyApplication quelque chose comme:

public static MyApplication getWebApplication() {
return (MyApplication) Application.get();
}

on peut alors écrire :

MyApplication.getMyApplication().myApplicationSpecificMethod();

Cela peut s’applique également à la Session, pour sûr.

voilà, trois fois rien, clairement, mais un pas de plus vers un code propre. Ca en vaut donc la peine (et ça m’évitera de me répéter sur le forum wicket développez.com, even better ;) ).

++
joseph

Tags:, ,

Playing with Wicket’s templates

janvier 28, 2010 3 commentaires

[Ce billet est également disponible en français.]

Wicket comes with some templating facilities. Nothing fancy, but still they’re quite handy, especially when integrating JavaScript components.

These functionalities aren’t much advertised and I would say even quite unknown. So, let’s dig in !

Basically, this templating is about some text containing variables, for example ${variable}, whose values are provided through Java code.

Let’s take a simple example, a template file named javascript.tpl containing :

alert('${variable}');

Wicket is nice enough to provide an easy to access the templates as package resources, through the PackagedTextTemplate class :

public PackagedTextTemplate(final Class clazz, final String fileName)

For example :

PackagedTextTemplate jsTemplate = new PackagedTextTemplate(this.getClass(), "javascript.tpl");

Thus the template can be next to the .html page and the Java class using it, making the whole quite cohesive.

Providing the variables is done through a simple Map :

Map parameters = new HashMap();
parameters.put("variable","test working");

And then, you most probably want to include this template in some html. Wicket provides you different options :

  • as an header contribution :
    add(TextTemplateHeaderContributor.forJavaScript(jsTemplate, new  Model((Serializable) parameters)));
  • directly next to some element in the html file:
    Java side :

    Label myScript = new Label("myScript", new JavaScriptTemplate(jsTemplate).asString(parameters));
    myScript.setEscapeModelStrings(false);
    add(myScript);

    Html side :

    <wicket:container wicket:id="myScript"></wicket:container>

You may have noticed that, in both cases, I didn’t provided the surrounding script tag (and the appropriate inner wrapping). Fear not, Wicket does it for you !
Indeed, the rendered html is :

<script type="text/javascript"><!--/*--><![CDATA[/*><!--*/
alert('test working');
/*-->]]>*/</script>

If the template was about some CSS stuff, one would just need to wrap it using a CssTemplate instead of the JavaScriptTemplate.

A bit more info are available there Including CSS resources.

++
joseph

Hibernate statistics page

janvier 4, 2010 1 commentaire

Hi !

We were some time ago at a presentation about hibernate (at a Karlsruhe JUG event). The presenter, Michael Plöd showed us a nice Hibernate statistics page. As you can bet, we went for reusing it :)

However, in doing so, some issues appeared, and in the end we thought the updated version could be useful for others as well.

It mainly consists in an adaptation to Wicket 1.4 (generics).

In details, the changes are :
- use of ReloadableDetachableModel, needed AFAIK by Hibernate statistics (they have transient fields in their objects, and as such I was getting NPE after refreshing the page),
- page completely "genericfied" : compact code, no more cast or compiler warning (the page was written for wicket 1.3 I presume),
- minor display issues fixed : date showed as a formatted date, executionMinTime’s default value (Long.MAX_VALUE) taken in account (and replace by zero),
- html closer from the "standards" : use of h1, thead and the like (which we use in our template, so…),
- in order to make the component injection independent, there is now a setter for the entity manager provider (consequently the actual page construction is made in "onBeforeRender"),
- the code is now in a panel, so it can be easily put in any application specific page.

Whatever, the code, quite long long, is following.


import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;

import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.stat.CollectionStatistics;
import org.hibernate.stat.EntityStatistics;
import org.hibernate.stat.QueryStatistics;
import org.hibernate.stat.SecondLevelCacheStatistics;
import org.hibernate.stat.Statistics;

public class HibernateStatisticsPanel extends Panel
{
private static final String DATE_FORMAT = "hh'h'mm dd.yy.MM";

private EntityManager entityManager;

private static DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);

public HibernateStatisticsPanel(final String id)
{
super(id);
}

@Override
protected void onBeforeRender()
{
if (!hasBeenRendered())
{
if (entityManager == null)
{
throw new IllegalStateException("The entityManagerProvider must be set.");
}
final CompoundPropertyModel model = new CompoundPropertyModel(
new LoadableDetachableModel()
{
@Override
protected Statistics load()
{

return getSessionFactory(entityManager).getStatistics();
}
});
setDefaultModel(model);

add(new Label("isStatisticsEnabled", new LoadableDetachableModel()
{

@Override
protected String load()
{
String result;
if (areStatsEnabled() == true)
{
result = "enabled" + " since " + dateFormat.format(new Date(model.getObject().getStartTime()))
+ " (" + DATE_FORMAT + ")";
}
else
{
result = "disabled";
}
return result;
}

}));

Link switchStats = new Link("switch_stats")
{
@Override
public void onClick()
{
final SessionFactory sessionFactory = getSessionFactory(entityManager);
sessionFactory.getStatistics().setStatisticsEnabled(
!sessionFactory.getStatistics().isStatisticsEnabled());
sessionFactory.getStatistics().clear();
final CompoundPropertyModel model = new CompoundPropertyModel(
new LoadableDetachableModel()
{
@Override
protected Statistics load()
{
return sessionFactory.getStatistics();
}
});
setDefaultModel(model);
}
};
switchStats.add(new Label("switchText", new LoadableDetachableModel()
{

@Override
protected String load()
{
if (areStatsEnabled())
{
return "Click to disable stats";
}
return "Click to enable stats";
}
}));
add(switchStats);

add(new Label("sessionOpenCount"));
add(new Label("sessionCloseCount"));
add(new Label("flushCount"));
add(new Label("connectCount"));
add(new Label("prepareStatementCount"));
add(new Label("closeStatementCount"));
add(new Label("entityLoadCount"));
add(new Label("entityUpdateCount"));
add(new Label("entityInsertCount"));
add(new Label("entityDeleteCount"));
add(new Label("entityFetchCount"));
add(new Label("collectionLoadCount"));
add(new Label("collectionUpdateCount"));
add(new Label("collectionRemoveCount"));
add(new Label("collectionRecreateCount"));
add(new Label("collectionFetchCount"));
add(new Label("secondLevelCacheHitCount"));
add(new Label("secondLevelCacheMissCount"));
add(new Label("secondLevelCachePutCount"));
add(new Label("queryExecutionCount"));
add(new Label("queryExecutionMaxTime"));
add(new Label("queryExecutionMaxTimeQueryString"));
add(new Label("queryCacheHitCount"));
add(new Label("queryCacheMissCount"));
add(new Label("queryCachePutCount"));
add(new Label("commitedTransactionCount"));
add(new Label("transactionCount"));
add(new Label("optimisticFailureCount"));

ListView entityStats = new ListView("entities",
new LoadableDetachableModel< List >()
{

@Override
protected List load()
{
String[] entities = model.getObject().getEntityNames();
List entityNames = new ArrayList();
for (int i = 0; i < entities.length; i++)
{
entityNames.add(model.getObject().getEntityStatistics(entities[i]));
}
return entityNames;
}
})
{
@Override
protected void populateItem(final ListItem item)
{
item.setModel(new CompoundPropertyModel(item.getModelObject()));

item.add(new Label("deleteCount"));
item.add(new Label("updateCount"));
item.add(new Label("fetchCount"));
item.add(new Label("insertCount"));
item.add(new Label("loadCount"));
item.add(new Label("optimisticFailureCount"));
item.add(new Label("categoryName"));
}
};
add(entityStats);

ListView collectionStats = new ListView("collections",
new LoadableDetachableModel()
{

@Override
protected List load()
{
String[] collections = (model.getObject()).getCollectionRoleNames();
List collectionNames = new ArrayList();
for (int i = 0; i < collections.length; i++)
{
collectionNames.add(model.getObject().getCollectionStatistics(collections[i]));
}
return collectionNames;
}
})
{
@Override
protected void populateItem(final ListItem item)
{
item.setModel(new CompoundPropertyModel(item.getModelObject()));

item.add(new Label("recreateCount"));
item.add(new Label("updateCount"));
item.add(new Label("fetchCount"));
item.add(new Label("removeCount"));
item.add(new Label("loadCount"));
item.add(new Label("categoryName"));
}
};
add(collectionStats);

ListView queryStats = new ListView("queries",
new LoadableDetachableModel< List >()
{

@Override
protected List load()
{
String[] queries = (model.getObject()).getQueries();
List queryNames = new ArrayList();
for (int i = 0; i < queries.length; i++)
{
queryNames.add(model.getObject().getQueryStatistics(queries[i]));
}
return queryNames;
}
})
{
@Override
protected void populateItem(final ListItem item)
{
item.setModel(new CompoundPropertyModel(item.getModelObject()));

item.add(new Label("cacheHitCount"));
item.add(new Label("cacheMissCount"));
item.add(new Label("cachePutCount"));
item.add(new Label("executionCount"));
item.add(new Label("executionRowCount"));
item.add(new Label("executionAvgTime"));
item.add(new Label("executionMaxTime"));
item.add(new Label("executionMinTime", new AbstractReadOnlyModel()
{

@Override
public String getObject()
{
// by default the hibernate stats. object put
// Long.MAX_VALUE to the executionMinTime, so we
// look for it and replace it by 0 where needed
long executionMinTime = item.getModelObject().getExecutionMinTime();
if (executionMinTime == Long.MAX_VALUE)
{
return "0";
}
return "" + executionMinTime;
}
}));
item.add(new Label("categoryName"));
}
};
add(queryStats);

ListView cacheStats = new ListView("caches",
new LoadableDetachableModel< List >()
{
@Override
protected List load()
{
String[] caches = model.getObject().getSecondLevelCacheRegionNames();
List cacheNames = new ArrayList();
for (int i = 0; i < caches.length; i++)
{
cacheNames.add(model.getObject().getSecondLevelCacheStatistics(caches[i]));
}
return cacheNames;
}
})
{
@Override
protected void populateItem(final ListItem item)
{
item.setModel(new CompoundPropertyModel(item.getModelObject()));

item.add(new Label("hitCount"));
item.add(new Label("missCount"));
item.add(new Label("putCount"));
item.add(new Label("elementCountInMemory"));
item.add(new Label("elementCountOnDisk"));
item.add(new Label("sizeInMemory"));
item.add(new Label("categoryName"));
}
};
add(cacheStats);

}
super.onBeforeRender();
}

private boolean areStatsEnabled()
{
return ((Statistics) getDefaultModelObject()).isStatisticsEnabled();
}

public static SessionFactory getSessionFactory(final EntityManager entityManager)
{
Object delegate = entityManager.getDelegate();
if (delegate instanceof Session)
{
Session session = (Session) delegate;

return session.getSessionFactory();
}
return null;
}

public void setEntityManager(final EntityManager entityManager)
{
this.entityManager = entityManager;
}

public EntityManager getEntityManager()
{
return entityManager;
}
}

And the html page :

<html>
     <wicket:panel>
              <h1>Hibernate Statistics</h1>
               Statistics are <span wicket:id="isStatisticsEnabled"/>.<br /> <a href="#" wicket:id="switch_stats"><span wicket:id="switchText"></span></a>
     <br /><br />
     <h2>Overview</h2>
    <table class="whiteGrey">
        <tr><th>sessionOpenCount</th><td><span wicket:id="sessionOpenCount"/></td></tr>
        <tr><th>sessionCloseCount</th><td><span wicket:id="sessionCloseCount"/></td></tr>
        <tr><th>flushCount</th><td><span wicket:id="flushCount"/></td></tr>
        <tr><th>connectCount</th><td><span wicket:id="connectCount"/></td></tr>
        <tr><th>prepareStatementCount</th><td><span wicket:id="prepareStatementCount"/></td></tr>
        <tr><th>closeStatementCount</th><td><span wicket:id="closeStatementCount"/></td></tr>
        <tr><th>entityLoadCount</th><td><span wicket:id="entityLoadCount"/></td></tr>
        <tr><th>entityUpdateCount</th><td><span wicket:id="entityUpdateCount"/></td></tr>
        <tr><th>entityInsertCount</th><td><span wicket:id="entityInsertCount"/></td></tr>
        <tr><th>entityDeleteCount</th><td><span wicket:id="entityDeleteCount"/></td></tr>
        <tr><th>entityFetchCount</th><td><span wicket:id="entityFetchCount"/></td></tr>
        <tr><th>collectionLoadCount</th><td><span wicket:id="collectionLoadCount"/></td></tr>
        <tr><th>collectionUpdateCount</th><td><span wicket:id="collectionUpdateCount"/></td></tr>
        <tr><th>collectionRemoveCount</th><td><span wicket:id="collectionRemoveCount"/></td></tr>
        <tr><th>collectionRecreateCount</th><td><span wicket:id="collectionRecreateCount"/></td></tr>
        <tr><th>collectionFetchCount</th><td><span wicket:id="collectionFetchCount"/></td></tr>
        <tr><th>secondLevelCacheHitCount</th><td><span wicket:id="secondLevelCacheHitCount"/></td></tr>
        <tr><th>secondLevelCacheMissCount</th><td><span wicket:id="secondLevelCacheMissCount"/></td></tr>
        <tr><th>secondLevelCachePutCount</th><td><span wicket:id="secondLevelCachePutCount"/></td></tr>
        <tr><th>queryExecutionCount</th><td><span wicket:id="queryExecutionCount"/></td></tr>
        <tr><th>queryExecutionMaxTime</th><td><span wicket:id="queryExecutionMaxTime"/></td></tr>
        <tr><th>queryExecutionMaxTimeQueryString</th><td><span wicket:id="queryExecutionMaxTimeQueryString"/></td></tr>
        <tr><th>queryCacheHitCount</th><td><span wicket:id="queryCacheHitCount"/></td></tr>
        <tr><th>queryCacheMissCount</th><td><span wicket:id="queryCacheMissCount"/></td></tr>
        <tr><th>queryCachePutCount</th><td><span wicket:id="queryCachePutCount"/></td></tr>
        <tr><th>commitedTransactionCount</th><td><span wicket:id="commitedTransactionCount"/></td></tr>
        <tr><th>transactionCount</th><td><span wicket:id="transactionCount"/></td></tr>
        <tr><th>optimisticFailureCount</th><td><span wicket:id="optimisticFailureCount"/></td></tr>           
    </table>
    
    <h2>Entity Statistics</h2>
    <table class="whiteGrey">
        <thead>
             <tr>
                 <th>Entity</th>
                 <th>Load Count</th>
                 <th>Fetch Count</th>
                 <th>Insert Count</th>
                 <th>Delete Count</th>
                 <th>Update Count</th>
                 <th>Optimistic Failure Count</th>
               </tr>
        </thead>
          <tbody>
            <tr wicket:id="entities">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="loadCount"/></td>
                <td><span wicket:id="fetchCount"/></td>
                <td><span wicket:id="insertCount"/></td>
                <td><span wicket:id="deleteCount"/></td>
                <td><span wicket:id="updateCount"/></td>
                <td><span wicket:id="optimisticFailureCount"/></td>
            </tr>
          </tbody>               
    </table>
    
    <h2>Collection Statistics</h2>
    <table class="whiteGrey">
        <thead>
             <tr>
                 <th>Collection</th>
                 <th>Load Count</th>
                 <th>Fetch Count</th>
                 <th>Recreate Count</th>
                 <th>Remove Count</th>
                 <th>Update Count</th>
               </tr>
        </thead>
          <tbody>
            <tr wicket:id="collections">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="loadCount"/></td>
                <td><span wicket:id="fetchCount"/></td>
                <td><span wicket:id="recreateCount"/></td>
                <td><span wicket:id="removeCount"/></td>
                <td><span wicket:id="updateCount"/></td>
            </tr>
          </tbody>
    </table>
    
    <h2>Query Statistics</h2>
    <table class="whiteGrey">
        <thead>
             <tr>
                 <th>Query</th>
                 <th>Execution Count</th>
                 <th>Execution Row Count</th>
                 <th>Avg Time</th>
                 <th>Min Time</th>
                 <th>Max Time</th>
                 <th>Cache Hit Count</th>
                 <th>Cache Miss Count</th>
                 <th>Cache Put Count</th>
               </tr>
          </thead>
          <tbody>
            <tr wicket:id="queries">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="executionCount"/></td>
                <td><span wicket:id="executionRowCount"/></td>
                <td><span wicket:id="executionAvgTime"/></td>
                <td><span wicket:id="executionMinTime"/></td>
                <td><span wicket:id="executionMaxTime"/></td>
                <td><span wicket:id="cacheHitCount"/></td>
                <td><span wicket:id="cacheMissCount"/></td>
                <td><span wicket:id="cachePutCount"/></td>
            </tr>
          </tbody>
    </table>
    
    <h2>Cache Statistics</h2>
    <table class="whiteGrey">
         <thead>
             <tr>
                 <th>Cache</th>
                 <th>Hit Count</th>
                 <th>Miss Count</th>
                 <th>Put Count</th>
                 <th>Elements in Memory</th>
                 <th>Elements on Disk</th>
                 <th>Size in Memory</th>
             </tr>
          </thead>
          <tbody>               
            <tr wicket:id="caches">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="hitCount"/></td>
                <td><span wicket:id="missCount"/></td>
                <td><span wicket:id="putCount"/></td>
                <td><span wicket:id="elementCountInMemory"/></td>
                <td><span wicket:id="elementCountOnDisk"/></td>
                <td><span wicket:id="sizeInMemory"/></td>
            </tr>
          </tbody>
    </table>
     </wicket:panel>
</html>

The guicy one is as simple as :


import javax.persistence.EntityManager;

import org.apache.wicket.markup.html.panel.Panel;

import com.google.inject.Inject;

public class GuicyHibernateStatisticsPanel extends Panel
{

@Inject
private EntityManager entityManager;

public GuicyHibernateStatisticsPanel(final String id)
{
super(id);
HibernateStatisticsPanel statisticsPanel = new HibernateStatisticsPanel("hibernateStatisticsPanel");
statisticsPanel.setEntityManager(entityManager);
add(statisticsPanel);
}

}

and :

<html>
     <body>
          <wicket:panel>
               <wicket:container wicket:id="hibernateStatisticsPanel" />
          </wicket:panel>
          
     </body>
</html>

Hope it helps :)
++

PS : I know the html code isn’t shown as best as it could, but I was fed up of trying to get wordpress to render the tags…

How to do UI test ?

novembre 3, 2009 Poster un commentaire

Me again…

Well, I just wanted to speak about an issue for which I didn’t find any proper answer yet.

The matter is gui testing. Currently, at work, we do proper unit test for "framework level code". By "framework code", I mean code being in some utilities or services projects which are put there to be reused. These tests are usually fairly easy to do, since the context is well known. Furthermore, as once as we change this code, these tests prove their value. As changes are made quite often, be it only when adding new stuff and thus refactoring, we feel like these tests are good bang for bucks.

For example, recently, we saw a bug in some on "framework code". We wrote a failing test for it (well, I’m not 100% sure of that, but we should have had anyway ;) ), fixed it and, on the go, spotted some possible improvements. We did them as well, then ran again all tests and we were pretty confident about it.

But, then, what about "project specific code" ?

Hum, what’s in it anyway ? Some "framework level code", for sure, which is already tested, and then some JPA, wicket and business related code. Testing the JPA code would basically mean testing the JPA layer, which is pointless. What about the GUI then ? Well, wicket isn’t exactly the easiest framework for it… The way its generates the ids make it hard(er) for most of the html test tools, like for example selenium. And even if, what to test actually ? Should we do some "clicks through" tests for all use cases ? All possible navigation paths ? And what about the data needed by these tests ? Using WicketTester, then we would be at pain with jquery heavy pages (well, I guess most of the selenium like frameworks are at pain in this case…). Overall, the efforts to put there are likely to be important.

So, what about the gain ? The odds that we’ll touch again these classes are quite low, way lower than framework level code. Basically, we don’t reuse these stuff. And if some functionalities/pages are fine enough, then we may even not touch them again before very long.

In the end, we have some layers which are, at the same time, harder to test and with less "return on investment". It looks like not testing these "project specific/gui layers" is a good idea…

However, which bugs the end users, tester or product owner are likely to see the first ? The gui bugs, for sure. Worse, whereas framework layer code tends to be mostly Java, the front end relies heavily on nice stuff like CSS, html and javascript. And as if refactoring wasn’t already hard enough there, Wicket, with its string based property and compound models, makes it even harder. So in the end, this gui layer is quite error prone and highly visible to the "customers/users"… Bad isn’t it ? And don’t forget all these quirks about back button, interruption of page loading, browsers inconsistencies and many ui possibilities… Even worse !

That’s where my issue with ui test lies… This ui layer is a pain to test, and would be tested mainly for just a development, without much reuse. But, it’s an error prone layer. And it’s the most visible one… So, if, like me, you’re kind of fond of tests and bullet proof code, then you might well share my dilemma…

What to do then ? Well, for the time, I’ve no proper answer. I guess I should try to spend more time testing my ui before giving it to the outer world, even it feels awkward to do it manually. For sure, exploratory tests might help, but again they’re manual and in no way comprehensive… No clear solution here for me, unfortunately…

Any hint deeply welcome ! In fact, I guess that’s the (hidden) aim of this post : maybe someone’ll come with some magic bullet(s). After all, Christmas is coming soon, so it’s dreaming time anyway ;)

++

Wicket : provide a body to your tags !

août 4, 2009 1 commentaire

Hi

We had a strange issue here : we created a component opened for extension. For some reason it was working with a Label but not a MultilineLabel. Crazy isn’t it ?
It turned out we declare this "opened" component like this :

The user of it should just override a callback method like this :

            @Override
            protected Component createContentComponent(final String id, final IModel model)
            {
                return new MultiLineLabel(id, model.getObject().getContent());
            }

So why wasn’t it working ?
Simple : MultilineLabel uses replaceComponentTagBody(markupStream, openTag, body);
Or my container didn’t have any body :

Simply switching to this solved the issue :

Conclusion : always provide a body for your components, you don’t know what’ll be done with them !
++

Tags:,
Suivre

Recevez les nouvelles publications par mail.

%d bloggers like this: