Thursday, December 22, 2011

MySQL failover talk

Hi! Today I had a talk at local IT user group regarding the MySQL fault-tolerance cluster. No original research neither anything specific - just a bit of experience which may help you setup the same easier ;).


Alternatively you can look at them locally here:


Wicket 1.4 modal window not hiding contents in hierarchy on close! :o

Looks like plenty of the posts are for myself not to forget about 'specific' issues found in the frameworks during the development. Well, wicket 1.4.(18?) this time.

The problem
So, we have a modal window. There is a form (call it form2) in this modal window. For the last, modal window itself is within a form (call it form1). Hierarchy looks like form1 <- modal window <- form2.

Both form1 and form2 have validators for their components, and both do have submitting components.

When the MW is shown and visible, everything works just fine according to the Nested form rules of the wicket. However, as you close the modal window by any means, the form2 will still be validated on the submit of form1! However, as it is _not_ visible on client (modal window is closed) we can not show to user what's wrong, they actually don't have access to the components failing.

Wicket modal windows do close by means of Ajax. E.g. when the actual DOM element is hidden by clicking cross, or outside modal window, or close button, wicket issues an ajax-call to server to notify it that window is no more visible.

This call is handled by WindowClose behavior, the subclass of AbstractAjaxDefaultBehavior.For greater detail you may take a look at it in the ModalWindow class. What this behavior does in the original implementation:
  1. Reset shown flag to false, so window will be opened again when next time show(target) is invoked.
  2. In case of the modal window showing pages, not components, do cleanup of the pagemap.
  3. Invoke WindowCloseListener instance (if any has been set to this ModalWindow).
That's it. It means that closing modal window does not hide the components inside it from the wicket hierarchy, and they are treated as visible however they are not really!

The solution
General solution for me was to hide content on the modal window close action.
2 approaches:
  1. Local.
    Just implement the windowClosedCallback, pass it to the instance of modal window, and in it, do hide your content on the close action.
    Bad things are that if you are developing a reusable component, you may want clients of this component to use windowClosedCallback for their own stuff.

  2. General.
    Subclass a ModalWindow. Say, MyModalWindow extends ModalWindow

  3. Override newWindowClosedBehavior() of it

  4. In this method, create your own ajax behavior doing same thing as wicket does, plus hide component if it is there. Pass it on to the return. Voila, we are done.
Now the components will be really hidden, and you won't be getting nested forms edgecase validation issues (or whatever else hiding there - I don't really know).

That's the code snippet:

//... inside our ModalWindow subclass, MyModalWindow
private class MyModalWindowClosedBehavior extends AbstractDefaultAjaxBehavior
implements
IWindowClosedBehavior {
private static final long serialVersionUID = 1L;

@Override
protected void respond(AjaxRequestTarget target) {
respondOnWindowClosed(target); // that'd be a call to the wicket's MW behavior.
if (getContent() != null) {
getContent().setVisible(false);
}
}

@Override
public CharSequence getCallbackScript() {
return super.getCallbackScript();
}
}

@Override
protected IWindowClosedBehavior newWindowClosedBehavior() {
return new PoseidonWindowClosedBehavior();
}

Tuesday, December 20, 2011

Inform user about Ajax failed (no network) in Wicket

The problem

It is well known to the internet. If you have an Ajax application you have to somehow inform user in case Ajax fails. And in the modern world Ajax may fail very easily - WiFi switched off, bad smartphone connection et al.

Also we have a Wicket which does hide all the Ajax work behind the scenes - you drop components to the page, they do communicate via Ajax.

The (possible) solution
At least we employed the one for us.

1. Create the localized message key in the property file. We are using XML properties, so it'd look like Ajax failed! Reload please

2. Create a function in one of the javascript files you always load. You may do it inline, but the function in the file makes it easier to change. My guess it should survive absence of the Wicket too.

AlertError here is the generic error function showing jQueryUI custom dialog.


function tryRegisterWicketAjaxOnFailure(message) {
if (!Wicket || !Wicket.Ajax) {
return;
}
Wicket.Ajax.registerFailureHandler(function() {
alertError(message, 0, 'center', true);
});
}


3. As our tryRegister takes a message parameter we need to pass it from the Wicket. So, in your BasePage.java or whatever code just add


add(new HeaderContributor(new IHeaderContributor() {
public void renderHead(IHeaderResponse response) {
response.renderOnDomReadyJavascript(
String.format("tryRegisterWicketAjaxOnFailure('%s')",
getString("ajaxCommunicationFailedMessage")));
}
}));
4. Just enjoy it. You may put 'refresh' button, or a spinner at this dialog which would constantly check if the network is back. It's up to your creativity and demand.

Saturday, December 17, 2011

Passing messages in method names? Why not!

Have been working with Mockito awesome mocking framework (i LOVE it). It also does use Hamcrest matcher as an option for matching its arguments.

So, say you don't want users of your library to implement interface directly but rather'd like force them to use the Base implementation of this interface and extend it instead? Putting away the reasons which led to this situation, here's how it may be accomplished:

package org.hamcrest;
public interface Matcher extends org.hamcrest.SelfDescribing {
boolean matches(java.lang.Object o);
void _dont_implement_Matcher___instead_extend_BaseMatcher_();
}

Every time you'll try to create a direct implementation of this interface, you'll get this weird method which would tell you what to do.

Good or bad? To me rather bad; something is wrong in design if you offer interface but don't allow users to use it. On the other side it may be good, as BaseMatcher gives some bells and whistles and you don't always know about good base subclass. Also, this may have been caused by some external reasons.

Thursday, December 15, 2011

Wicket DownoadLink and AjaxDownload with non-ascii filenames

The problem - yet unsolved for me :(

You have an Apache Wicket application (in my case, this is wicket 1.4.x). You have a page which asks user to download some content and then lets download it. Content is dynamically generated (file?). It should have a name (default one) on client's machine. Name may have cyrillic (or Chinese) symbols.


Solutions
First of all, I was unable to solve this completely :(

Coming from StackOverflow post, the good way to do would be to form the URL ending with a filename (URL-encoded), and show no filename in Content-Disposition header. All other tricks and tips work not well under different browsers.

Wicket usual approach is to use either DownloadLink or AjaxDownload (thank you guys for such a good solution! Liked it much). First of them is using usual wicket URL coding strategy and second - pretty much the same, being a AjaxBehavior and thus IBehaviorListener. That far I could see no real way to change these URLs. The issue is that both of those don't allow for custom URLs, they are actually plugged into Wicket's infrastructure of page-component-iBehaviorListener-RequestCycle-RequestCodingStrategy chain, so we don't have direct control over it.

One solution proposed by Martin Grigorov on the wicket-users mailing list was to use
http://wicketinaction.com/2011/07/wicket-1-5-mounting-resources/ for mounting, but that did not work out for me as I needed to use containing page's model to generate the actual data.

Fail :(
Will up if any luck.

Wicket this time

another incarnation, switching to wicket-related posting as we're doing wicket jobs at the moment.

Hopefully experiences shared here would save someone's time -- that's a good enough reward.