Mittwoch, 10. August 2011

Creating a Custom Wicket Tag Resolver

When building pages with Wicket the wicket:message tag is very handy for adding localized text to said page. What I don't like about it is the fact that you are limited to
using properties. The problem about that is when you need to use large portion of text a property file gets very bloated, very ugly to read and incredibly ugly when trying to add formatting. From a maintenance point of view resoruce should be
structured very clearly and especially when using xml properties an auto formatter is very handy, which can also cause ill formatted results.

On workaround is to split your text in several properties and then just add them one by one to your markup/components but this is very cumbersome. So I figured it would be cool if you were able to use
includes in the same way as properties with the message tag where you could externalize a layouted portion of text with format information. But to my knowledge no such tag exists, so I took this as an opportunity
to find out how to build my very own tag resolver :-)

Unfortunately I did not find a lot of information about this topic which meant I had find out the hard way, by looking at existing Wicket classes and the obvious choice was WicketMessageResolver, so what
you will see next is heavily inspired by this class even though I did change quite a few portions.

These are the basics that need to be done:

  • your class has to implement IComponentResolver
  • in a static initializer block make your tag well known by calling WicketTagIdentifier.registerWellKnownTagName(tagname)
  • in your application class register your resolver using getPageSettings().addComponentResolver(new YourResolver())
  • in the resolve method instantiate a subclass of MarkupContainer and add it to the resolvers container
  • make sure the subclass' isTransparentResolver() method always returns true, thus he can access it's parent's children as if they were it's own
  • and as a last you override the markup container's onComponentTag() method where you can put all the logic you need to handle your tag, create the rendered result and put it into the response object

And here my complete IncludeResolver, I hope this will be useful to someone someday. And as usual I would be greatful for comments, additions and corrections on this :-)

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Response;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupElement;
import org.apache.wicket.markup.MarkupException;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.WicketTag;
import org.apache.wicket.markup.html.include.Include;
import org.apache.wicket.markup.parser.filter.WicketTagIdentifier;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.apache.wicket.model.Model;
import org.apache.wicket.response.StringResponse;
import org.apache.wicket.util.lang.PropertyResolver;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.string.interpolator.MapVariableInterpolator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is a tag resolver to handle tags of the following format
 * <wicket:include [key="xxx"|filename="xxx"]/>.
 * 

* This class tries to provide the same functionality for included html files as * WicketMessageResolver does for property file entries but also * tries to avoid to fail early. *

* You have to specify either a key or a filename. A key will be looked up via * the resource mechanism and the result will be used as filename. *

* In addition you can nest tags within this tag. Those child tags will then be * used to generate the replacements for variables defined in the file content. * If this fails for a variable the parent's model object will queried for a * property with the variable's value as name. Should this also fail the last * fall back is the same query on the parent object. *

* If the resource settings are set to throw exceptions on missing properties * this class will raise an exception if either a variable cannot be looked up * or a child component is not replaced. *

* This tag allows you to easily include files in your pages which can also * depend on your localized properties. *

* Usage: *

* *

*     <wicket:include key="myFile">
 *        This text will be replaced with text from the specified file.
 *        <span wicket:id="replaceme">[to be replaced]</span>.
 *     </wicket:include>
 * 
* * Then your property file needs an entry like this: * *
* myFile = path / to / file
 * 
* * This file contains html fragments with variables: * *
* This is a file with ${replaceme}.
 * 
* * And your java component add: * *
* add(new Label("replaceme", new Model<String>("some replaced text")));
 * 
* * The output will be: * *
* This is a file with some replaced text.
 * 
* * @author Chris * */ public class IncludeResolver implements IComponentResolver { /** * */ private static final long serialVersionUID = -9164415653709941809L; private static final String tagname = "include"; private static final Logger log = LoggerFactory.getLogger(IncludeResolver.class); // register the tagname static { WicketTagIdentifier.registerWellKnownTagName(tagname); } /** * Handles resolving the wicket:include tag. If a filename is specified this * file will be looked up. If a key is specified it's value is looked up * using the resource mechanism. The looked up value will then be used as * filename. * */ public boolean resolve(MarkupContainer container, MarkupStream markupStream, ComponentTag tag) { if ((tag instanceof WicketTag) && tagname.equalsIgnoreCase(tag.getName())) { WicketTag wtag = (WicketTag) tag; String fileName = StringUtils.trimToNull(wtag.getAttribute("file")); String fileKey = StringUtils.trimToNull(wtag.getAttribute("key")); if (null != fileKey) { if (null != fileName) { throw new MarkupException( "Wrong format of : you must not use file and key attribtue at once"); } fileName = StringUtils.trimToNull(container.getString(fileKey)); if (null == fileName) { throw new MarkupException("The key inside could not be resolved"); } } else if (null == fileName) { throw new MarkupException( "Wrong format of : specify the file or key attribute"); } final String id = "_" + tagname + "_" + container.getPage().getAutoIndex(); IncludeContainer ic = new IncludeContainer(id, fileName, fileKey, markupStream); ic.setRenderBodyOnly(container.getApplication().getMarkupSettings().getStripWicketTags()); container.autoAdd(ic, markupStream); return true; } return false; } /** * Helper class to break open the original Include class by using * inheritance. * * @author Chris * */ private static final class ExposingInclude extends Include { /** * */ private static final long serialVersionUID = -212861726226482365L; public ExposingInclude(String id, String filename) { super(id, filename); } public final String expose() { return this.importAsString(); } } /** * Container class to render the included file. * * @author Chris * */ private static final class IncludeContainer extends MarkupContainer { /** * */ private static final long serialVersionUID = 3991477945186422264L; private final String key; private final String fileName; public IncludeContainer(String id, String fileName, String key, MarkupStream markupStream) { super(id, new Model(fileName)); this.key = key; this.fileName = fileName; } /** * Wrapper to log this instance's settings. * * @return String containing the instance's settings. */ private final String logParams() { return " tag with " + (null != this.key ? "key = '" + this.key + "' and resulting " : "") + "fileName = '" + this.fileName + "'"; } /** * Renders the component tag. The base content is read from the file * defined by the filename. Afterwards all containing variables are * replaced by the following methods in this order: *
    *
  • trying to get the value from a child component that has the
    * variable's value as id
  • *
  • trying to get the value as property from the parent's model
    * object
  • *
  • trying to get the value as property from the parent object
  • *
* * If all attempts to lookup up variable fail or not all child tags are * replaced either an exception is thrown or a warning is logged * depending on the resource settings. * */ @Override protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) { // get all child tags' rendered contents Map children = this.getRenderedChildren(markupStream, openTag); // read the included file content String fileContent = new ExposingInclude(this.getId() + "_internalInclude", this.getDefaultModelObjectAsString()).expose(); // remember all replaced child tags final Set replacedChildren = new HashSet(); // substitute all variables and set the result as response this.getResponse().write(new MapVariableInterpolator(fileContent, children) { @Override protected String getValue(String variableName) { // try to get value from rendered child tags String value = super.getValue(variableName); if (null != value) { // if we used a child tag create a marker replacedChildren.add(variableName); } else { if (log.isDebugEnabled()) { log.debug(IncludeContainer.this.logParams() + " - Could not find replacement value for variable = '" + variableName + "' in child tags"); } // see if the variable can be replaced using the // parent's model object - use try/catch to avoid fail // early try { value = Strings.toString(PropertyResolver.getValue(variableName, IncludeContainer.this.getParent().getDefaultModelObject())); } catch (WicketRuntimeException e) { if (log.isDebugEnabled()) { log.debug(IncludeContainer.this.logParams() + " - Could not find replacement value for variable = '" + variableName + "' in parent model"); } } } if (null == value) { // see if the variable can be replaced using the parent // object - use try/catch to avoid fail early try { value = Strings.toString(PropertyResolver.getValue(variableName, IncludeContainer.this.getParent())); } catch (WicketRuntimeException e) { if (log.isDebugEnabled()) { log.debug(IncludeContainer.this.logParams() + " - Could not find replacement value for variable = '" + variableName + "' in parent object"); } } } if (null == value) { // Handle failed lookup String logMsg = IncludeContainer.this.logParams() + " - Bailing with exception since variable = '" + variableName + "' could not be replaced"; if (IncludeContainer.this.isThrowingExceptions()) { if (log.isDebugEnabled()) { log.debug(logMsg); } markupStream.throwMarkupException(IncludeContainer.this.logParams() + " - Could not replace variable = '" + variableName + "'"); } else { log.warn(logMsg); } } return value; } }.toString()); // Make sure all of the children were rendered Iterator iter = children.keySet().iterator(); while (iter.hasNext()) { String id = iter.next(); if (replacedChildren.contains(id) == false) { String msg = "The for file " + this.fileName + "has a child element with wicket:id=\"" + id + "\". You must add the variable ${" + id + "} to the file content for the wicket:include."; if (this.isThrowingExceptions() == true) { markupStream.throwMarkupException(msg); } else { log.warn(msg); } } } } /** * Wraps call to resource settings to determine if a exception should be * thrown in case not all variables are resolved or not all children are * rendered. * * @return * Application.get().getResourceSettings().getThrowExceptionOnMissingResource() */ private final boolean isThrowingExceptions() { return Application.get().getResourceSettings().getThrowExceptionOnMissingResource(); } /** * Walks through all children of the current tag and stores their * rendering results in a map. * * @param markupStream * The markupStream associated with the tag. * @param openTag * The current include tag to be rendered. * @return Map containing all children's rendered responses associated * with each child's id. */ private Map getRenderedChildren(final MarkupStream markupStream, final ComponentTag openTag) { Map children = new HashMap(); if (!openTag.isOpenClose()) { while (markupStream.hasMore() && !markupStream.get().closes(openTag)) { MarkupElement element = markupStream.get(); // If it a tag like or if ((element instanceof ComponentTag) && !markupStream.atCloseTag()) { String id = ((ComponentTag) element).getId(); Component comp = this.getParent().get(id); if (comp != null) { children.put(id, this.getRenderedResponseString(markupStream, comp)); } else { markupStream.next(); } } else { markupStream.next(); } } } return children; } /** * Obtains the rendered response of the given component rendered * according to the markupStream. Rendering uses a temporary response * and the original response is restored before returning the result. * * @param markupStream * The markupStream used to render the component. * @param comp * The component to be rendered. * @return The rendered result. */ private String getRenderedResponseString(final MarkupStream markupStream, Component comp) { Response webResponse = comp.getResponse(); StringResponse response = new StringResponse(); try { this.getRequestCycle().setResponse(response); comp.render(markupStream); } finally { this.getRequestCycle().setResponse(webResponse); } return response.getBuffer().toString(); } /** * * @see org.apache.wicket.MarkupContainer#isTransparentResolver() */ @Override public boolean isTransparentResolver() { return true; } } }

Mittwoch, 3. August 2011

Firefox Plugin Pitfall

Today I did learn something about firefox plugins. We had some problems with a plugin provided by a 3rd party vendor that we integrated in our site.

The plugin did not install properly in firefox instance of version 4 or later. The respective extension got listed in the extension manager but the plugin itself did not show up in the list of installed plugins. This meant the plugin was not available and our site still asked for it to be installed. In firefox 3.6 everything was okay.

After a lot of research and some peeking around I finally found the solution. From firefox 4 onwards an extension has to declare if it needs to be unpacked by firefox to work or not. In versions older than 4 every extension was unpacked but in newer versions unpacking requires the presence of the unpack tag in the install.rdf packaged with every xpi file.

As described at the mozilla documentation the install.rdf needs to contain this entry:<em:unpack>true</em:unpack>

When I unzipped the xpi file in question and extracted the contained install.rdf I saw that it did contain the unpack tag but with a subtle differece: <em:unpack>True</em:unpack>

As you see the only difference is the uppercase "T". It was a long shot but since I had nothing to lose I changed the entry to "true" and repackaged the xpi. After putting up this new version to our website the plugin installed perfectly in every firefox version!!

Two things here really threw me off, first how our vendor could ship this version as this file cannot have every worked for firefox 4 for which it was explicitly built (but I will get back at them about this ;-)). And secondly why firefox does not recognize the entry just because of the case.

Anyways I had a hard time figuring this one out today so maybe this can be of use to someone :-)

Sonntag, 31. Juli 2011

Gnocchi-Gemüse-Pfanne

So, heute mal ein ganz anderes Thema, mein selbstkreiertes Rezept für eine leckere Gnocchi-Gemüse-Pfanne in Käsesauce :-)

Zutaten:

Die Mengenangaben orientieren sich an den erhältlichen Portionen meines örtlichen Supermarkts und können nach Belieben angepasst werden. Die hier angegebenen Mengen ergeben 4-6 Portionen.

1/2 Blumenkohl
1/2 Brokkoli
1 Bund Suppengrün (3-4 Karotten, etwas Selerie und Lauch sowie Petersilie)
1 Packung Schmelzkäse
200 ml Sahne
250 ml Milch
600g Schnitzelfleich
500g Gnocchi
Salz
Pfeffer
Gemüsebrühe
Muskat

Zubereitung
  • Blumenkohl, Brokkoli, Suppengrün und Fleisch mundgerecht schneiden
  • Blumenkohl und Brokkoli ca. 10 Minuten bissfest kochen
  • Suppengrün (ohne Petersilie) in eine große Pfanne mit heißem Öl geben und bei starker Hitze etwas anbraten
  • Das Fleisch in die Pfanne geben und bei mittlerer Hitze mitbraten, dabei öfters gut durchrühren
  • Wenn alles gut angebraten ist mit Sahne und Milch ablöschen gut durchrühren und Hitze wieder hochdrehen
  • Wenn die Sauce am köcheln ist den Schmelzkäse darin unter kräftigem Rühren auflösen
  • Inzwischen sollten Blumenkohl und Brokkoli fertig sein, also abgiesen und neues Wasser aufsetzen
  • Blumenkohl und Brokkoli in die Sauce geben und mit Salz, Pfeffer, Brühe, Muskat und Petersilie abschmecken
  • Sobald das Wasser kocht Salz und die Gnocchi hinzugeben
  • Wenn die Gnocchi fertig sind, wieder abgiesen und danach auch in die Pfanne geben
  • Nun alles weiterköcheln lassen und umrühren bis die Sauce die gewünschte Konsistenz hat
Wie üblich würde ich mich über Feedback oder Verbesserungsvorschläge freuen

Ansonsten, Guten Appetit!! :-)

Mittwoch, 13. Juli 2011

Validating Nested Forms in Wicket

Wicket provides a wide range of functionality and automatisms for creating and processing forms and thus makes handling user input very easy at least in most cases. When it comes to nested forms though some things might need some tweaking.

First of I want to say that the wicket documentation provides some very useful information about Nested Forms and Conditional Validation which covers this topic pretty much. But unfortunately one case is not really addressed there which I would like mention here.

I stumbled across this when I created two forms nested within each other where the inner form manipulated the content of the outer one and the outer form submits it's data to the backend while ignoring the inner form. So far no problem, but after adding some validators to the inner form submitting the outer form failed. As can be seen on the page for Nested Forms this is the desired default behavior, a form validates all it's children and thus also it's nested forms. So I had a look at Conditional Validation to see what the recommended way was to change this.

At first I tried to use IFormVisitorParticipant interface to isolate my two forms since this seemed the cleanest solution as the inner form would decide for itself if it wants to be validated or not in the given context. But it did not work for me. The problem was that the inner form did not only have validators assigned to it's components but also to itself and these form level validators were still executed.

So I had to look further. The Form class has a validate method that would be perfect for this purpose if it could be overwritten but for some reason (and I am sure it is a good one) this method is final. Another method that is called during validation of a component hierarchy is the isEnabled method but touching it has a lot of impact on basically all phases of a compoents life cycle and you cannot determine if the method is called during validation or another phase like rendering. And this might cause your component to be disabled or worse.

A suggestion from the wicket user mailing list was to use onFormSubmitted on the outer form to remove the validators from the inner form before submitting it and then adding them again. I did not like this idea since in my opinion it clearly breaks cohesion and the law of demeter.

So I decided to disable default form processing on the outer form and perform the validation of it's child components manually. But this also has the implication that the child components inputs are not put into the forms object model by the framework so you have to do this step yourself too.

All in all this is just minor anoyance but it would be pretty cool if IFormVisitorParticipant could be extended in a later release to completely disable a forms validation without affecting other life cycle phases. But I am not sure yet if this is at all possible. So for now we have to live with this ;-)

Samstag, 9. Juli 2011

Self Logging Exception Pattern

Error handling is an important and vital part of any serious application and usually a fair amount of application code addresses this topic. This is too some extend due to the fact that proper error handling requires to consider two aspects.

When ever an error occurs usually the control flow gets iterrupted and needs to take a different route. In Java this is done by throwing, catching, rethrowing and wrapping exceptions so that the application either aborts the current request to show an error message to the user or to recover from the error automatically.

Additionally those errors need to be logged to allow the support staff to see what is going on in the application and to be able to tell customers why their actions did not give the expected result and most importantly to find bugs.

This often leads to code blocks like the following:

UserTransaction transaction = null;
EntityManager em = null;
try {
  transaction = (UserTransaction) new 
      InitialContext().lookup("java:comp/UserTransaction");
  transaction.begin();
  em = emf.createEntityManager();
  em.merge(this.be);
  transaction.commit();
} catch (IllegalStateException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (RollbackException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (IllegalArgumentException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (TransactionRequiredException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (PersistenceException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (NamingException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (NotSupportedException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (SystemException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (SecurityException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (javax.transaction.RollbackException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (HeuristicMixedException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} catch (HeuristicRollbackException ex) {
  logger.error("Persist failed", ex);
  throw new ApplicationException("Persist failed", ex);
} finally {
  em.close();
}

Quite anoying, isn't it? Now there are certain possibilities to try to reduce this boiler plate. The first attempt that is still employed far too often is to just catch Exception. This pattern is higly discouraged because it swallows exceptions resulting from coding errors like the very common NullPointerException. Another way to make the code a little bit cleaner is to wrap the log statement and exception generation into a convinience method that might also be parameterized with a message to be used as log ouput and the exceptons detail message:

private void bail(String msg, Throwable ex){
  logger.error(msg, ex);
  throw new ApplicationException(msg, ex);
}

...
} catch (SystemException ex) {
  this.bail("Error message 1", ex);
} catch (SecurityException ex) {
  this.bail("Error message 2", ex);
} catch (javax.transaction.RollbackException ex) {
  this.bail("Error message 1", ex);
} catch (HeuristicMixedException ex) {
  this.bail("Error message 1", ex);
...

But what if you do not only want to control the exception text to be displayed but also the kind of exception to be thrown. One could swap the error message for an enum and then decided within the bail method which exception should be thrown or to avoid this additional boiler plate add the exception class to the enum and then generate the exception by using reflection.

But all these approaches still rely on the programmer to make sure that where ever he handles the original excpetion he has to do both, log the error and throw the exception. Even if this is wrapped in a utility class that might be used throughout the application. Don't get me wrong, this approach is usually pretty fine and works rather well, at least as long as all developers involved in the project are aware of this necessity and can be trusted not to forget it. But often enough even the most reliable developers are in a hurry or the project staff changes or...

So I thought it would be nice to have mechanism that makes sure that when ever an application sepcific exception is thrown the corresponding error will be logged. That is why I came up with this idea:

public class ApplicationException extends RuntimeException {

  private static final long serialVersionUID = -4599465800805791368L;
  private static final Logger logger = Logger.getLogger(ApplicationException.class);

  private MessageCode code;

  private List<Object> params;

  public ApplicationException (MessageCode code) {
    this(code, null, null);
  }

  public ApplicationException (MessageCode code, List<Object> params) {
    this(code, null, params);
  }

  public ApplicationException (MessageCode code, Throwable cause) {
    this(code, cause, null);
  }

  public ApplicationException (MessageCode code, Throwable cause, List<Object> params) {
    super(getResolvedMessage(code.getMsg(), params), cause);
    this.code = code;
    this.params = params;
    this.log();
  }

  private void log() {
    StackTraceElement ste = this.getStackTrace()[0];
    if (logger.isEnabledFor(this.code.getLevel())) {
      logger.log(this.code.getLevel(), "ApplicationException thrown at "
        + ste.getClassName()
        + "."
        + ste.getMethodName()
        + ":"
        + ste.getLineNumber()
        + " with message '"
        + getResolvedMessage(this.code.getMsg(),this.params) 
        + "'");
    }
  }

  public String getKey() {
    return this.code.getKey();
  }

  private static String getResolvedMessage(String msg, List<Object> params) {
    if (null != params) {
      for (int i = 0; i < params.size(); i++) {
        if (null != params.get(i)) {
          msg = msg.replaceAll("\\{" + i + "\\}", params.get(i).toString());
        }
      }
    }
    return msg;
  }
}


public enum MessageCode {

  // General
  CODING_ERROR("Error.CodingError", "This is very likely a bug"),

  // Persistence
  PERSISTANCE_SAVE_FAILED("Error.Persistance.SaveFailed", "Error during entity persitance."),

  // User
  USERNAME_ALREADY_TAKEN("Info.User.NameAlreadyTaken", "Username '{0}' already taken", Level.INFO), 
  INVALID_PASSWORD("Info.User.LoginFailed", "Login failed due to an invalid password for user '{0}'", Level.INFO);

  private String msg;

  private String key;

  private Level level;

  private MessageCode(String key, String msg) {
    this(key, msg, Level.ERROR);
  }

  private MessageCode(String key, String msg, Level level) {
    this.key = key;
    this.msg = msg;
    this.level = level;
  }

  public String getKey() {
    return this.key;
  }

  public Level getLevel() {
    return this.level;
  }

  public String getMsg() {
    return this.msg;
  }
}


Here the MessageCode class takes several arguments, a key that defines the property key to be used when displaying an error message to the user, a string containing information for developers that will be used as log statement and an optional level defining the log level for the message. The latter is useful since not everything that causes request abortion is an error like a user providing an invalid password.

The exception class wraps that MessageCode and creates a log statement within it's constructor. Of course this has a draw back, most logging frameworks like e.g. log4j allow to automatically augment the log message with the class name and line number where the message was logged. With the log statement being created within the exception that piece of informaton is lost. To get around this limitation I am using the exeptions own stacktrace by accessing it's first element that contains the class name and line number where the exception constructor was invoked.

That way it is possible to log an error and abort request execution with one statement in your application code and by incooperating this capability into the exception class itself you can create your application specific exception hierarchy that inherits this behavior.

So far I am only using this pattern in my private projects but I am thinking of also introducing it to my customers' projects.

As always I would be very interessted in what you guys think of this approach especially if you see any problems with it or have suggestions for improvements.

Donnerstag, 7. Juli 2011

"Breaking Wicket ajax with broken markup" or "How to shoot yourself in the foot - Part 1"

Another day, another screw up ;-)

Here a short example how you can break Wicket ajax pretty easy but spend a lot of time on figuring out what went wrong.

I had a small page with a form in it and button issuing an ajax request. When clicking the button elements got added to the form using a PropertyListView. Not a hard task so it was set up pretty quick and did work like a charm. But then I thought "Would be nice to reuse this form on a few other pages as well." and decided to wrap it into a panel (panels are cool since they allow you to encapsulate the grouped components as well as their markup and properties).

Building the panel was also done real quick and after embedding it into my page instead of the original form I fired up my application again to see how well I had done and gloat a bit in front of.. we.. basically myself ;-)

I hit my nifty ajax button and BAMM!!! - FULL PAGE RELOAD!!!

After a short episode of wtfs and other cussing I tried to find out what the problem was. Conviniently the server logs showed exactly.. nothing. No errors, no warnings. Debugging the ajax call also did not work because the debugger did not get into any breakpoint, which showed that the ajax code did not get executed anymore.

Next I tried to check my changes but of course everything looked fine... (oh how wrong I was..)

So grabbed my last straw, the ajax debug window - savior of ajax screw ups all around the world. I opened it and started to fill the form fields, where each click got shown in the debug window but as soon as I hit the former ajax button the windows closed down - D'OH!

After trying it a few times I was able to spot an error message being displayed in the debug window before it closes. But unfortunately I could not see what the exact message was. So again I went double checking the java code and markup and suddenly my mistake revealed itself to me.

When I exchanged the form for the new panel in my page I forgot to change the markup tag the component was mapped to. And because the tag was associated to a form it was a form tag in the original version. But that form tag was now moved inside the panel and by forgetting to replace the page's form tag with a span tag the final markup ended up with a form wrapped inside a form.

After makeing the tag a span everything was working fine again. Having nested form tags is of course totally beyond any specifiaction etc. but I was a bit surprised that in effect this made my ajax request to behave like a regular page reload without any exceptions or anything like that.

And yes, this was somewhat embarrasing, but maybe someone else screws up the same way and this post helps him (or her) to save some time.. or just gives someone a good laugh *g*

Dienstag, 5. Juli 2011

Some Of My Custom Validators for Wicket

Currently I am having a closer look at the Wicket web framework which has some very nice aspects. One of it is it's validation support.There are plenty of tutorials and howtos on the net about how to build your own validators and how to configure them etc. So it would be mute to write yet another one of those (if you think differently let me know, but I doubt that pretty much ;-)).

Wicket has quite a few built in validators for a decent range of every day tasks that you need in most web applications. Those include length and range validations as well as equality of input fields (very useful for password handling) and much more. But I came across some cases that I found no suitable validators readily available. So I built a few of my own validators and would like to share the implementation of two of them because I think those could be useful for other people too.

The first validator is useful for something like a form to change a users password. In this case you want the user to enter a different password than his existing one. Wicket provides you with the EqualPasswordInputValidator to check if two password fields contain the same text but not for one to check the opposite, so here is my attempt for this task.

package org.butterbrot.rankem.validation;

import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.validation.AbstractFormValidator;
import org.apache.wicket.util.lang.Objects;

/**
 * Checks for two text components to contain different values.
 * 
 * 
 * @author Chris
 * 
 */
public class UnequalPasswordsValidator extends AbstractFormValidator {

 /**
  * 
  */
 private static final long serialVersionUID = 5677456180685647137L;
 private final FormComponent[] components;

 @SuppressWarnings("unchecked")
 public UnequalPasswordsValidator(FormComponent formComponent1, 
                        FormComponent formComponent2) {
  if (formComponent1 == null) {
   throw new IllegalArgumentException("argument formComponent1 cannot be null");
  }
  if (formComponent2 == null) {
   throw new IllegalArgumentException("argument formComponent2 cannot be null");
  }
  this.components = new FormComponent[] { formComponent1, formComponent2 };
 }

 public FormComponent[] getDependentFormComponents() {
  return this.components;
 }

 public void validate(Form form) {
  final FormComponent formComponent1 = this.components[0];
  final FormComponent formComponent2 = this.components[1];

  if (Objects.equal(formComponent1.getInput(), formComponent2.getInput())) {
   this.error(formComponent2);
  }

 }

}

Another one covers a more specialized use case. Assume you have a form where the user has to enter some numeric values like defining an interval or some kind of slice (maybe a portion of a video or a range) and you want to make sure that two values have a certain difference in their values. This validator allows you to check exactly that.

package org.butterbrot.rankem.validation;

import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.validation.AbstractFormValidator;

/**
 * Checks if the contents of two number components differ at least the specified
 * difference. If the difference has a positive value the first component must
 * contain the lesser value and vice versa. Since the check internally operates
 * on the double representations of the given values this class should not be
 * used for BigDecimal components.
 * 
 * @author Chris
 * 
 */
public class NumberDifferenceValidator extends AbstractFormValidator {

 /**
  * 
  */
 private static final long serialVersionUID = -1237357103535636603L;
 private final FormComponent[] components;
 private final Number difference;

 @SuppressWarnings("unchecked")
 public NumberDifferenceValidator(FormComponent formComponent1,
   FormComponent formComponent2, Number difference) {
  if (formComponent1 == null) {
   throw new IllegalArgumentException("argument formComponent1 cannot be null");
  }
  if (formComponent2 == null) {
   throw new IllegalArgumentException("argument formComponent2 cannot be null");
  }
  if (difference == null) {
   throw new IllegalArgumentException("argument dfference cannot be null");
  }
  this.difference = difference;
  this.components = new FormComponent[] { formComponent1, formComponent2 };
 }

 public FormComponent[] getDependentFormComponents() {
  return this.components;
 }

 public void validate(Form arg0) {
  if (this.components[1].getConvertedInput().doubleValue() - 
            this.components[0].getConvertedInput().doubleValue() < this.difference
    .doubleValue()) {
   this.error(this.components[0]);
  }

 }

}


Of course I would be grateful for any comments on what you think of these examples or how these could be improved.