Thursday, November 30, 2006
The First Braindead Thing I've Seen in Wicket
ListView
.Getting a list of links to display in a
ListView
is easy enough. The downside is that to change the contents of the text of a link, you have to include inside it a <span> element and assign to that a Label
and then change the contents of that. This seemed like a lot of running around just to change the contents of a PageLink
to me, so I figured I'd have a go at simply extending PageLink
to something that can set the contents of the body of the link tag to any arbitrary string.The normal way one would do this is by overriding the
onComponentTagBody
method, but unfortunately doing this for PageLink
is impossible. For whatever braindead reason, Wicket makes onComponentTag
and onComponentTagBody
final inside Link
.This is incredibly short-sighted if not outright stupid. There are a myriad of reasons why someone (like me) might want to extend the behavior of a
Link
, including the way the <a> tag and its body are rendered. Maybe one wants to add some random Javascript behavior, change the contents of the tag body, add a dynamic title
attribute, etc. All impossible because someone made these methods final (for no good reason that I can see).Stupid.
Labels: software
Saturday, November 25, 2006
bullet shattered window at airtrain
police shooting outside airtrain
Friday, November 24, 2006
the statue of liberty's butt
Huh-huh
up close
Multimedia message
statue of liberty from battery park
Thursday, November 23, 2006
just my luck.
Wednesday, November 22, 2006
ed sullivan theater
Monday, November 20, 2006
Clever Title Using The Word "Fragments"
Without fragments, the only way to have conditional markup where the contents of that markup are manipulated by wicket would be to have multiple classes and multiple markup files. That's a lot of work for an insignificant difference in the content of the page. Wicket provides, however, and we have a construct like this:
<span wicket:id="mugshot">(mugshot)</span>
<wicket:fragment wicket:id="hasMugshot">
<img src="/images/posters/Gorthak.png" alt="[Gorthak mugshot]" class="mugshot" wicket:id="mug" />
</wicket:fragment>
In the code we can say something like this:
if (mugPath != null) {
Fragment mugfrag = new Fragment("mugshot", "hasMugshot");
mugfrag.add(new Label("mug", "") {
private static final long serialVersionUID = 1L;
@Override
protected void onComponentTag(ComponentTag tag) {
System.out.println("onComponentTag called!");
tag.getAttributes().remove("src");
tag.getAttributes().add("src", mugPath);
tag.getAttributes().remove("alt");
tag.getAttributes().add("alt", mugUser + " mugshot");
}
});
add(mugfrag);
} else {
add(new Label("mugshot","").setEnabled(false));
}
Now I probably could have accomplished something similar in this simple case by just setting up the img tag with a wicket:id and setting that element visible or invisible. Notice what the fragment lets us do, however- something we can't do with plain markup in Wicket. We can ignore the fragment completely in the Java code (as in the
else
block above). Similarly, we can conditionally use one <wicket:fragment> from an arbitrary number of them if we choose.One thing to keep in mind is that when you're adding components in the Java class that are encapsulated by a <wicket:fragment>, you're adding them to a
Fragment
not to the page. As you can see, adding a Fragment
involves giving the name of the place you want the wicket:fragment to go and the name of the fragment itself.You can see I'm doing some crazy inner-class trickery for my
img
tag. Wicket's Image
class expects to refer to an image file that's contained within the framework. In my case, the image is actually out on the filesystem as part of the static content of the web site. So I've basically extended the Label
class to deal with the needs of an img
tag, in this case replacing the contents of the src
and alt
attributes when the tag is rendered. I'll probably refactor this out into its own class at some point- there's really more code than I'd like to see in an anonymous inner class there, and I could see reusing it in other places.Another thing I want to work on is a strategy for copying in/out values from the data model to the service bean classes. I'm thinking that annotations+reflection might be an effective and clean way of doing it.
I resolved my earlier pondering about the article table trigger in PostgreSQL. It is as simple as this:
ELSIF (TG_OP = 'UPDATE') THEN
IF (NEW.section != OLD.section OR
NEW.title != OLD.title OR
NEW.adjective != OLD.adjective OR
NEW.summary != OLD.summary OR
NEW.body != OLD.body) THEN
INSERT INTO article_audit SELECT nextval('article_audit_audit_id_seq'), now(), 'U', NEW.*;
END IF;
RETURN NEW;
Duck soup!
Labels: software
Sunday, November 19, 2006
Basic article submission works; thoughts on service layer
Stuff that isn't working yet includes checking for duplicates, handling conditional creation based on a user's status (ie, have they been approved to skip the approval process?) and UID/open-proxy checks.
I also want to make the Preview page use a Wicket Panel for the article; that way I can reuse that panel to display the article.
It dawned on me today that my plan to use triggers to create an audit history for articles needs a second look. The trigger is going to fire every time someone votes an article up or down, mails the article, etc. I don't really want these events to cause an insert in the audit table... only changes to the text of the article should do that. Then there's the question about whether these belong in the service layer or on the database.
One thing that's disappointingly missing from JPA is a way to assign the sequence numbers to new objects. Toplink provides this facility in the full product, but as far as I can tell, JPA offers no way to do it. This is annoying from a service standpoint because it means the only way I can get at the new object ID is to commit the transaction, which kind of prevents chaining together multiple activities in one transaction.
I've seen this discussed before among people trying to figure out service-oriented architectures. One school of thought is that you just don't try to make several activities part of one transaction, but instead provide a method to undo any action that was taken. So for every create, there is a destroy, and for any update, you keep the old state in case you need to update it back to the original values.
Another possibility is to provide multiple versions of calls- one that's public facing that takes no transaction as an argument, and then internal versions that allow a running transaction to be passed in. Then any chaining of events must happen at the service level (ie, there's a single separate service call for the chain of events) with the transaction still managed by the service.
I had to do this because I needed to get back an ID for the article that was just created, and the only way was to commit the transaction, due to the absence of an assignIds() call.
Labels: software
Friday, November 17, 2006
Alternative Method of Passing Along Model Objects
Rather than calling
setResponsePage(PreviewBeforeSubmit.class)
, it is also possible to say setResponsePage(new PreviewBeforeSubmit(modelObject))
, then define a constructor in the PreviewBeforeSubmit
class that takes the model object. This does mean that to go back your originating page must also define a similar constructor, and its no-arg constructor may need to do the original instantiation (or your WebApplication
could).This strategy saves a few bytes in your session, since the model object is already in there. It means you won't need a second class in the session to contain the first.
Labels: software
Thursday, November 16, 2006
Poop Back and Forth
Fortunately, Wicket makes this easy. Surprised?
I created a new HTML page called PreviewBeforeSubmit.html and of course its corresponding PreviewBeforeSubmit.java. I created all the appropriate labels and a tiny form with just two submit buttons. Then I ran into a head-scratcher: I can set my response page easily enough from the creation form with
setResponsePage
, but how does my completed ArticleBean
model get over there?The answer lies in the use of a
WebSession
which we create by overriding public ISessionFactory getSessionFactory()
in the WebApplication. I'm beginning to form an appreciation for simple and small anonymous inner classes now, despite the hate I professed for them earlier. I think as long as they're kept tiny they're probably not too evil. In this case, here's the entire definition:
public ISessionFactory getSessionFactory() {
return new ISessionFactory() {
public Session newSession() {
return new ArticleSession(ArticleCreateApp.this);
}
};
}
(In this case, the anonymous inner class is an interface implementation. Since it's anonymous there's absolutely no chance of being retarded and sticking the suffix impl on the end of the class name.)
ArticleSession
is quite simple. We get Eclipse to generate constructors from the super class (WebSession
) that just call super
and beyond that it's just a simple bean that holds an ArticleBean
with the corresponding accessor methods.Then, rather than constructing a new
ArticleBean
in the creation form, we get it from the ArticleSession
(which instantiates it in the declaration). In the preview page, we simple retrieve this same object from the ArticleSession
. Wicket takes care of getting the ArticleSession
into and out of the low-level session object, in this case HttpSession
. Note that the session is associated with the web application, not any particular page.The first time I tried this strategy, I got a big nasty exception that was rooted in a ClassCastException. This turned out to be because I still had my old session sitting around on disk, which contained a default implementation of a
WebSession
. The solution is to just delete Tomcat's sessions file (after stopping Tomcat) so that a new session is built.Managing the two buttons on the small form on the preview page is easy, because the
Button
object has an onSubmit
method you can override, which of course can execute setResponsePage
to move you anywhere you'd like. With the buttons in place, going back and forth between the form and the preview is trivial, and pretty quick-- even with the HTML tidying in the form validation, it takes less than half a second (usually around 0.3s) to get between pages.The other tidbit from tonight's work is that if you want HTML to pass through to the page literally, it is necessary to
setEscapeModelStrings(false)
on the associated Label
.Tuesday, November 14, 2006
Something to Remember with Generic Authentication
WebApplication
and require authentication for all instances of WebPage.class
. It seemed to work at the time, but that was because I had already logged in, so the required stuff was already in my session.The problem is that redirecting to
Signin.class
means redirecting to a WebPage
, and that web page comes under the control of the same WebApplication
that demands having authentication for all WebPage
s. You can imagine what happens. If you can't, I'll tell you: an infinite redirect, as it demands authentication for the authentication form.The nice solution to this is to create a subclass of
WebPage
, call it AuthenticatedWebPage
, implement all of its constructors to call super(...)
and make the pages that require authentication extend AuthenticatedWebPage
instead of WebPage
, and configure the SigninAuthorizationStrategy
class to work on classes that are assignable from AuthenticatedWebPage
.(Naturally
Signin
should not extend AuthenticatedWebPage
.)Labels: software
Monday, November 13, 2006
Adding Generic Authorization to a Wicket Application
Unfortunately the wicket example page for sign-in is very single-application-centric. It uses a Session object created via overriding the
WebApplication
's getSessionFactory()
method and then it performs authentication via methods in that Session
object. This works if you only have one application on your web site, but it falls down pretty quickly if you want to do web-site-wide authentication. I want my users' logins to work for all applications once they've signed in once. I accomplish this now by sticking a User
object in the session once a login is completed successfully.Unfortunately, Wicket doesn't have a notion of a thing higher in its hierarchy than an
WebApplication
. There are WebApplication
s which are generally made up of one or more WebPage
s which in turn are generally comprised of many WebComponent
s. (I'm simplifying of course.) There is no notion of a global thing that exists outside the set of WebApplication
s. Furthermore, a Session
object is not a javax.servlet.Session, but rather a Wicket invention that abstracts away things like getting the HttpSession
from the request and putting things in it. These Session
objects are, again, very WebApplication
-specific; you create them by coding up a subclass and returning it via a session factory.So how do we get something to live in the
HttpSession
outside of the influence and control of any single application but still usable from those applications? The answer lies in this rather ugly piece of code:
HttpSession session = ((WebRequest)(WebRequestCycle.get().getRequest()))
.getHttpServletRequest().getSession();
Even though the Wicket framework tries to abstract away many things and handle a lot of menial plumbing for you, it doesn't completely tie your hands in the process. If you need to stick something in the user's HTTP Session, you still can... but beware-- once you step outside the framework, you're on your own for things like type checking, null handling, serialization, etc.
Because Wicket finds
WebPage
s via their class, it's quite easy to create a new package and put the class and HTML markup needed for the signin form in it. I started by taking these directly out of the Wicket example.The next step was to create an implementation of
IAuthorizationStrategy
(there's that stupid I
prefix again). This is the way authorization is wired into an Application: getSecuritySettings().setAuthorizationStrategy(
instance of IAuthorizationStrategy);
That goes in the
WebApplication
's init()
method. In the Wicket example they create this on-the-fly with an anonymous inner class. I think anonymous inner classes are hideous. What, was it too hard for you to click "New --> Class" in your IDE? Do you have to pay for your disk storage by the bit? No tabs in your editor? Honestly, make a damn concrete class. It won't kill you, and it makes your code far easier to read.I created a class called
SigninAuthorizationStrategy
. I didn't stick the abbreviation "impl" in the name, because, like prefixing interface names with I
, that behaviour is stupid and pointless. You know by the fact that the class declaration says "implements
" that it's a bloody implementation.As Wicket adds
Components
to your WebApplication
, it calls your public boolean isInstantiationAuthorized(Class componentClass)
method, passing in the class of the component that it's adding. This happens recursively, so you'll get a call for the WebPage
, then every WebComponent
on the web page. We don't want to have to authorize every single component on the page, so we pass in the class of components we do want to authorize and ignore the others:
private Class> componentTypeToAuthorize;
public SigninAuthorizationStrategy(Class componentType) {
this.componentTypeToAuthorize = componentType;
}
public boolean isInstantiationAuthorized(Class componentClass) {
if (!(componentTypeToAuthorize.isAssignableFrom(componentClass))) {
return true;
}
We return true because we are assuming that we're doing authorization at a high level of containment; that is to say, we're authorizing at the page level, and if the page is authorized, then its contained components are assumed to be authorized as well. There's no point in spending the cycles authorizing them too. The code goes on to say:
UserBean userBean = Utils.getUserBean();
if (userBean != null && userBean.isAuthenticated()) {
return true;
}
throw new RestartResponseAtInterceptPageException(SignIn.class);
}
Utils.getUserBean()
is just a convenience method to handle that ugly HttpSession
bookkeeping we saw earlier. The rest is pretty straightforward: if there's a UserBean
in the HttpSession
and it has been properly authenticated, then we authorize this component (in this case a WebPage
). Otherwise, we throw the quite-verbosely-named RestartResponseAtInterceptPageException
passing it the class name of the page we want to use as the intercepting page.SignIn.class
in turn handles the web form for signing in, authenticating via the UserService
and adding the resulting UserBean
to the user's session. Once this is complete, Wicket automatically resumes control where it left off via this call:
if (!continueToOriginalDestination())
{
setResponsePage(WebRequestCycle.get().getApplication().getHomePage());
}
If for some reason we're unable to continue to the original destination, we divert to the application's home page instead. This should probably not ever happen.
You can find the full source on the Tally-Ho project homepage. Look in net.spatula.news.ui.signin and net.spatula.news.article.create.
Labels: software
Friday, November 10, 2006
A new name, a new license, a new home...
Named, of course for The Village newspaper from the classic television series, The Prisoner.
The code will be released under the Apache 2.0 license, a departure from the former Spatula Public License. Much as I like my SPL, it has some flaws. My main goals in creating that license were to make my code GPL-phobic, because I despise the GPL and any other license whose creators intend to spread like a virus.
Of the free, open source licenses, the Apache 2.0 license comes the closest to my goals, requiring anyone deriving something from my project to leave my copyright notice intact and provide a NOTICE file indicating that they've made changes, though for their own contributions and code they may use whatever license they choose. I hope they choose to also use the Apache license, not the GPL abomination.
Having the Apache license allows me to manage the project with Google Code using Subversion. This is the project's new home. I've checked in the code I've written so far. Keep in mind that this is very much a work in the very early stages of progress. It has quite some way to go before it's in a state that's usable by anybody.
Labels: software
Wednesday, November 08, 2006
I Want Your SAX; JPA ResourceManager
onmouseover
or onclick
in their tags) is a bit trickier still. Getting HTML from end users who may not be terribly familiar with HTML constructs and don't always write the cleanest HTML is even more of a challenge.My rules for HTML in articles are pretty simple. You must enclose paragraphs with the <p> tag. The allowed tags are p, a, em, strong and blockquote. The only one of these that can have attributes is the <a> tag, and the only attribute I'll allow is
href
. It's fairly restrictive, but articles are mostly just paragraphs with the occasional link anyway. Anything else might interfere with the flow or style of the page.To deal with the malformed HTML question, I turned to JTidy, as I have in the past. JTidy is good at dealing with many common HTML coding problems, like dropped end tags, inlines that span blocks, and other common errors. It is not good at dealing with dropped angle brackets and dropped quote marks. For my purposes it is normally adequate. I wrote a small wrapper class around Tidy which also allows users to leave out the paragraph tags and leave blank lines to denote paragraphs... paragraph tags are inserted on their behalf. Additionally, it sets up some options:
tidy.setMakeClean(true);
tidy.setWord2000(true);
tidy.setLogicalEmphasis(true);
tidy.setDocType("strict");
tidy.setShowWarnings(false);
tidy.setQuiet(true);
tidy.setXHTML(true);
tidy.setDropEmptyParas(true);
There's also a "demoronize" method to change instances of <br /><br /> to paragraphs.
Unfortunately, Tidy does not remove 100% of the cruft; it can let attributes and tags with namespaces sneak through when you're outputting a document in XHTML or XML, which we need for proper page parsing. Tidy also won't remove arbitrary tags or attributes. A second phase of cleaning is needed.
For my second phase, I decided to try SAX. I have worked with DOM before, but the lightweight nature of SAX appealed to me for this part of the project. SAX can operate on a stream and you never need to have the entire XML document in memory. The way one commonly interacts with the SAX parser is to override the
DefaultHandler
class, implementing methods that are called by the parser when particular events occur during the parsing of a document. Typically you'd implement startDocument, startElement, endElement, characters, ignorableWhitespace
and possibly endDocument
.SAX doesn't really give you a way to alter the document it's parsing (to do so would generally require holding the document in memory, something for which you'd want to look at DOM). If you need to construct a new document, that's up to you. It does provide another mechanism for coding filters which can be chained, and that chain could certainly end with an all-purpose document-writing filter. But this is left up to you.
I decided to take the simplest approach possible and created a class called AllowedTag, which holds a tag name and the list of associated attributes I'll allow on that tag. I didn't go to the effort of differentiating between allowed and required attributes or attributes which might be mutually exclusive, leaving that work up to JTidy. I just want to strip the tags which offend me.
As SAX reads the end user's HTML, it makes calls to my implementation of
startElement
, passing in the namespace URI, fully qualified tag name, raw tag name, and a set of attributes in an Attributes
object.Then I check the tag name against my map of
AllowedTag
s. If it's allowed, I append a tag opener to my buffer and step through the Attributes
, adding those which are allowed by the AllowedTag
object. For anything which is not allowed, I record an error and increment an error count as I ignore the entity when building the new document.Characters and ignorable whitespace are simply appended to the buffer. For end tags, I perform the same check as in the start tag method, with one additional gotcha: self-closing tags like <br /> fire
startElement
and immediately fire endElement
. In order to preserve these tags as-is without changing them to something like <br></br>, it is necessary to track the last tag seen and the position in the output buffer after that tag was inserted. Then when handling an endElement
one can check to see if the buffer position has not changed and that the tag name is the same; if so, just change the last character (which will be >) to />
.Normally when operating with SAX and trying to do anything any more complicated with this, one would be pushing elements onto a stack with
startElement
and characters
and then popping data with endElement
. In this case what we're doing is so incredibly simple that a stack of depth > 1 is not necessary (the self-closing trick is functionally equivalent to a depth 1 stack).After finishing these classes (jUnit test-driven of course) I wired them into a Wicket validator, grossly abusing the validation framework by again letting it rewrite the data model if errors were found and changes were necessary (like illegal tags or attributes used) and by calling
error(String)
on the form's article body component to insert the very-specific error messages generated by the Tidy wrapper and the SAX tag cleanser.I also found a flaw last night in my plan to wrap the
EntityManagerFactory
in a singleton ResourceManager
. I'm not completely foiled, but some additional work may be needed. It can happen that an EntityManagerFactory
fails to initialize properly or may become invalid due to database connectivity problems. When this happens, that EMF refuses to cooperate any further; you have to destroy it and create a new one. So we can't blindly hold this in a singleton without doing some checks to ensure that it's still valid and functioning. The thing to do might be to move the createEntityManager
to the ResourceManager
and include the option to destroy and reconnect with some number of retries in that part of the code.Labels: software
Monday, November 06, 2006
MSM is Bad for My Palms
What I did *not* connect it to at the time was my joint supplement, which contains Glucosamine, Chondroitin and MSM (Methylsulfonylmethane). I had started taking this joint supplement after taking up massage, since it is desirable to protect one's joints with any repetitive movement and certainly any compressive ones, as we have with deep tissue work.
I stopped taking Mucinex and stopped using that particular sunscreen... but my hands and feet didn't return to normal. I began to worry, especially after a year or so, that I had somehow caused myself permanent skin damage.
After a while I switched from using a harsh astringent face wash to a glycerin-based, gentle one. I stopped using hair product, in case there was something in that clogging my pores. No dice. I even switched shampoos to see if that helped. No change.
I began to suspect MSM when I remembered that my dad has an allergic reaction to the sulfites (sulfur dioxide) in wine. My mom also mentioned that she has problems with sulfur compounds and couldn't take MSM without breaking out in hives. Then I poked around on the 'net a bit and found that in some people, MSM could cause skin rash or "thin skin". There was certainly enough information to be suspicious of MSM, which is called by some, "natural sulfur."
So about a month ago I stopped taking my joint supplement, and now my hands are nearly back to normal. They sweat again. They produce oil again. They're no longer dry and scaly. Dry hands begone.
Now it's possible that it wasn't the MSM and may have been the Chrondroitin, since Chondroitin is typically in the form of Chondroitin Sulfate, yet another sulfur compound. Glucosamine is sometimes packaged in the form of glucosamine sulfate (HCl is another option). It could also be that the combination of ingredients leads to simply too much sulfur, and I may better tolerate smaller quantities.
The next step is to try taking a Glucosamine/Chrondroitin supplement without the MSM and see if the symptoms return. I'm not looking forward to the possibility of my hands drying out again, but I am looking forward to the possibility of protecting my joints.
Better than AJAX: adding client-side-only behavior to form components
My first attempt at this was to write out the necessary Javascript by giving a <script> tag a wicket:id and then defining a corresponding
Label
in my form code. If you go this route, you must set label.setEscapeModelStrings(false)
; otherwise, Wicket will turn instances of " into ". Using this strategy, you can dynamically generate Javascript to be included in-place on the page. This is all well and good, but it means that any time you want this behaviour, you have to remember to include a <script> tag, sort out the HTML ids yourself, and then worry about multiple drop-downs defining the same function multiple times. This strategy is also not very reusable.A better solution is to add a behaviour or as the Wicket folks spell it, a behavior. (Years upon years of watching and reading British science fiction have made their impression upon me.) I came across behaviors initially when I added the
AjaxFormComponentUpdatingBehavior
and again when I was wondering how to get my Javascript into the <head> of a document. It turns out that wicket provides HeaderContributor.forJavascript
just for this purpose... and as it happens, HeaderContributor
is itself a subclass of AbstractBehavior
.Implementing a behavior is a matter of extending
AbstractBehavior
and implementing the methods that you need. Typically one will implement bind(Component)
and onComponentTag(Component, ComponentTag)
. It becomes somewhat important to understand the internal Wicket lifecycle, as you can guess from these names. bind
is called when your behavior is initially bound to a Component. onComponentTag
is called during rendering. It's important to realize that during rendering, you can no longer change the Component, or you'll get a ConcurrentModificationException
or the like... but you can make changes to the ComponentTag.Another "gotcha" is that the markup ID for a component is not generated until rendering time. I am told this is changing in Wicket 2. It's a bit of a limitation for this case; to inject Javascript into the document head, we have to modify a Component. The Javascript needs to know the HTML id of the tag (which corresponds to a Wicket Label) it is updating. For Wicket 1.2, we have to settle for specifying the HTML id of the Label's tag manually and supplying it to our behavior when we add it to our
DropDownChoice
. This also means that the Label
cannot have setOutputMarkupId(true)
, because that would override the manually-specified HTML id. We can still figure out the id of the DropDownChoice
via the onComponentTag
method, because the getMarkupId
method will return the correct markup id by that time.Initially I had just used
StringHeaderContributor
to contribute both the static Javascript function and the dynamically-generated per-drop-down mappings, but Wicket offers a better way to inject the Javascript, using the forementioned HeaderContributor.forJavascript(Class class, String path)
method. This allows you to place your Javascript file in the same package as your behavior class and pass that class and the filename of the Javascript. Wicket handles inserting the Javascript code reference and the Wicket servlet takes care of extracting and sending the file from your package in response to a request for it. This is probably a tiny bit less efficient than referencing an absolute path on the server, but the benefit is including the code in the same place and being able to easily edit it on the fly from your IDE.The Javascript is fairly straightforward, though I had to get some help writing it. Here it is:
var updateMap = new Object();
function updateLabel(list, label) {
var mylist = document.getElementById(list);
var mylabel = document.getElementById(label);
var selectedItem = mylist.options[mylist.selectedIndex].text;
mylabel.innerHTML = updateMap[label + '.' + selectedItem];
}
Right now it doesn't guard against conditions like an entry not being found in that updateMap, but that should be easy enough to add. As you can see, it takes the name of a drop-down list and the name of the HTML entity it should update, looks up the current value from the drop-down in the updateMap, and sets the value. The mappings are dynamically generated by
DropDownLabelUpdateBehavior
, which is supplied with a List
of beans, the names of the fields to access in those beans to get the value for the map and the data associated with that value, and the HTML id of the field it should update. After all this explanation, the code itself seems pretty simple:
package net.spatula.news.ui.behaviors;
import java.lang.reflect.Method;
import java.util.List;
import wicket.Component;
import wicket.behavior.AbstractBehavior;
import wicket.behavior.HeaderContributor;
import wicket.behavior.StringHeaderContributor;
import wicket.markup.ComponentTag;
public class DropDownLabelUpdateBehavior extends AbstractBehavior {
private static final long serialVersionUID = 1L;
private static final String javaScriptStart = "<script type=\"text/javascript\">\n";
private static final String javaScriptEnd = "</script>\n";
private String labelName;
private String value;
private String id;
private List beans;
public DropDownLabelUpdateBehavior(List beans, String labelName,
String idName, String valueName) {
this.beans = beans;
this.id = idName;
this.value = valueName;
this.labelName = labelName;
}
public void bind(Component component) {
component.add(HeaderContributor.forJavaScript(this.getClass(),
"dropDownLabelUpdateBehavior.js"));
component.setOutputMarkupId(true);
component.add(new StringHeaderContributor(javaScriptStart
+ getMappings() + javaScriptEnd));
}
public void onComponentTag(Component component, ComponentTag tag) {
tag.getAttributes().remove("onchange");
tag.getAttributes().add(
"onchange",
"updateLabel('" + component.getMarkupId() + "', '" + labelName
+ "')");
}
private String makeGetterName(String field) {
String upCaseFirstLetter = field.substring(0, 1).toUpperCase();
String rest = field.length() > 1 ? field.substring(1) : "";
return "get" + upCaseFirstLetter + rest;
}
private String getMappings() {
if (id == null || id.length() < 1 || value == null
|| value.length() < 1) {
return "";
}
String idMethod = makeGetterName(id);
String valueMethod = makeGetterName(value);
StringBuffer map = new StringBuffer(100);
for (Object object : beans) {
Method getId;
Method getValue;
String thisid, thisvalue;
try {
getId = object.getClass().getMethod(idMethod, (Class[]) null);
getValue = object.getClass().getMethod(valueMethod,
(Class[]) null);
thisid = (getId.invoke(object, (Object[]) null)).toString();
thisvalue = (getValue.invoke(object, (Object[]) null))
.toString();
} catch (Exception e) {
// Just make a best effort, and skip the element if anything
// goes wrong.
continue;
}
map.append("updateMap[\"" + labelName + "." + thisid + "\"] = \""
+ thisvalue + "\";\n");
}
return map.toString();
}
}
A few things to note: we turn on setOutputMarkupId on the component so that it'll be inserted in the HTML in the same way as we see it in
onComponentTag
, and any existing onchange
attribute in the generated ComponentTag
has to be removed first; calling tag.getAttributes().add
does not seem to overwrite an existing value. One thing that would be good to add is a check in bind
to ensure that the Component
is compatible with this behavior.Once all of this is in place, the code to fetch the
Section
list and add this behavior looks like this:
EntityManager em = ResourceManager.getInstance().getEMF()
.createEntityManager();
ListsectionsList = SectionService.getInstance()
.getSections(em);
em.close();
sectionDescription.setOutputMarkupId(false); // absolutely don't want
// it overwritten
if (article.getSection() != null && article.getSection().getId() != 0) {
sectionDescription.setModelObject(article.getSection()
.getDescription());
}
add(sectionDescription);
DropDownChoice sectionChooser = new DropDownChoice("section",
new PropertyModel(article, "section"), sectionsList,
new ChoiceRenderer("name", "id"));
sectionChooser.add(new DropDownLabelUpdateBehavior(sectionsList,
"sectionDescription", "id", "description"));
add(sectionChooser);
There you have it: a reusable behavior for updating an HTML field when the selection in a
DropDownChoice
changes.Labels: software
Saturday, November 04, 2006
Wicket Validation Revisited; AJAX Comes to morons.org; Unit Testing
validate
method saying "THIS IS NOT PART OF THE PUBLIC API". One wonders why the method is public in that case. The Wicket API for
IValidator
suggested extending CustomValidator
to do a custom validator, but CustomValidator
is deprecated. I guess their Javadoc is a bit outdated. Instead, the thing to do is to extend StringValidator
, a class for which you can override the onValidate
method. The main difference I can see is that this passes you both the FormComponent
and the String data associated with the model for that component... saving you having to call component.getInput()
yourself.After hunting through the available completions for a bit, I found the methods
setModelValue(String)
and setModelValue(String[])
for setting a single-value attribute or a multi-value attribute respectively. This is one of those few times one is reminded that what we're actually always dealing with are strings sent via HTTP. Some things are single strings, like text fields and check boxes; some things are arrays of strings, like multi-select boxes.Now the code for my
NoHtmlValidator
looks like this:
package net.spatula.news.ui.validators;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import wicket.markup.html.form.FormComponent;
import wicket.markup.html.form.validation.StringValidator;
public class NoHtmlValidator extends StringValidator {
private static final long serialVersionUID = 1L;
private static final Pattern pattern = Pattern.compile(".*</?[-\\w]+[^>]+>.*",
Pattern.DOTALL);
public void onValidate(FormComponent component, String data) {
Matcher matcher = pattern.matcher(data);
if (matcher.matches()) {
error(component);
data = data.replaceAll("</?[-\\w]+[^>]+>", "");
component.setModelValue(data);
}
}
}
You can see I've moved the
Pattern
up into the class; there's no sense in spending the overhead of compiling that Pattern
more than once. The key change here is that if you did include HTML tags in your field, the validator now strips those tags for you.My next task was to get the Article Section select box (
DropDownChoice
in Wicket terms) populated. For this, I created a SectionService
singleton class. Borrowing an idea from an Oracle JPA tutorial I also created a singleton ResourceManager
class for creating the EntityManagerFactory
. Provided anything needing an EntityManagerFactory
gets it from the ResourceManager
class, this has the effect of ensuring that across the application, I only ever create one EntityManagerFactory
. This is definitely desirable while testing, since with the JPA RI (Glassfish), the JDBC connection pool is managed by the EMF. There's no sense in creating more than one of these.I was able to fairly quickly create my service. Initially I had it return a
List
of KeyValuePairs
for the section ID and the section name. It was trivial to populate the DropDownChoice
. There was still a problem, however. My section names are fairly terse, and if I'm to have my readers able to submit articles at-will, they are going to need some guidance about which section they should use for their articles.This seemed like a good job for AJAX.
Wicket has some built-in AJAX goodies that are perhaps not quite as easy to use as Google's web toolkit, but they have the advantage of being integrated into the framework. To get AJAX behaviour out of the Sections
DropDownChoice
, we add AjaxFormComponentUpdatingBehavior
to the choice. It turns out that the easiest way to do this is with an anonymous inner class, as demonstrated on the Wicket Wiki. My code looks like this:
// In the Form class, declare the objects you are going to change from an AJAX action,
// in this case, a Label called sectionDescription.
private ArticleBean article = new ArticleBean();
private Label sectionDescription = new Label("sectionDescription", "");
// Later on as you're building your form...
EntityManager em = ResourceManager.getInstance().getEMF()
.createEntityManager();
ListsectionsList = SectionService.getInstance()
.getSections(em);
em.close();
sectionDescription.setOutputMarkupId(true);
if (article.getSection() != null && article.getSection().getId() != 0) {
sectionDescription.setModelObject(article.getSection()
.getDescription());
}
add(sectionDescription);
DropDownChoice sectionChooser = new DropDownChoice("section",
new PropertyModel(article, "section"), sectionsList,
new ChoiceRenderer("name", "id"));
sectionChooser.add(new AjaxFormComponentUpdatingBehavior("onchange") {
private static final long serialVersionUID = 1L;
protected void onUpdate(AjaxRequestTarget target) {
sectionDescription.setModelObject(article.getSection()
.getDescription());
target.addComponent(sectionDescription);
}
});
add(sectionChooser);
So what's going on here? First, I get my List of sections from my service. You can see that I had to abandon
KeyValuePairs
for SectionBeans
, since I needed not only the ID and name, but the description also. Then, we have to setOutputMarkupId(true)
so that Javascript can manipulate the HTML object by name. Next there is code to statically render the section description if it is already known; for example, after a form submission in the case that the form needs to be re-displayed.Then there is code to create the
DropDownChoice
. Because I want the rendered option values to correspond to the section IDs on the database, I use a ChoiceRenderer
, giving it the names of the fields to access from the objects in the list to get the display text and the value to associate with that text as the DropDownChoice
iterates through the list.Finally, we add the
AjaxFormComponentUpdatingBehavior
to the DropDownChoice
component. The "onchange" Javascript event is specified, and in the anonymous inner class extending AjaxFormComponentUpdatingBehavior
, onUpdate
is overridden. Just as we did when rewriting a field with a validator, we can rewrite the contents of the simple String
model of the sectionDescription Label
to the description of the SectionBean
held in the Article
. Notice that we're asking the article-- the backing model-- itself for this SectionBean
object; we're not querying the component.I happily loaded my web page and clicked on the drop-down. Nothing happened. I looked at the page source, and indeed there was Javascript markup on that <select> tag. I popped open the Javascript console and found it full of errors: "wicketAjaxPost is not defined." I looked closer at the source and sure enough, there was no script loading to define that function. After some digging, I came to realize that the culprit was Sitemesh. Wicket puts the declarations to load the Javascript code (which it serves out via its servlet) in the <head> section of the HTML document, which Sitemesh happily strips. The solution is to include a <decorator:head /> tag in the Sitemesh decorator at the end of its <head> section.
One thing I tried to do with AJAX in Wicket initially proved not to work. I wanted to make a <br /> tag come and go depending on whether a choice had been made in the drop-down. Unfortunately, there seems to be some problem with changing the value of
setVisible()
with AJAX, at least on a <br>. I ended up just putting the section description after the drop-down box, which turned out to be nicer anyway, since it doesn't change the vertical size of the table when the text gets rendered. UPDATE: it doesn't work because AJAX is operating on the client side; in other words, because the initial state is not visible, there's no markup sent for the <br /> to the client, which in turn means it's impossible for AJAX to tell where to put it when it's added. The solution is to wrap it in something that IS sent, like a containing <span> and repaint the <span>. Next I want to start looking deeper at
WicketTester
, which helps you write unit tests for Wicket pages and forms, helping you test validators and page contents without having to run a servlet container. If it works, this could be the holy grail of web application UI testing, something that has never had a great solution. (And if you think HTTPUnit
is a great solution, there is something the matter with you.) Maybe I can get a decent unit test for NoHtmlValidator
going. It won't have been test-first, but the other validators can be now.Labels: software
Thursday, November 02, 2006
Attention Toplink Authors
Consider this one: "Unknown abstract schema type [Foo]" where "Foo" is some class. What does that actually mean? It means Toplink couldn't find a class called Foo defined in persistence.xml. So why couldn't the bastards just SAY that? Why did they have to say "unknown abstract schema type"?
Attention Toplink Authors, or at least those of you who write these fucked-up error messages. I have a little task for you. There's something I want you to do. Ready for it?
Die in a fire as you eat my ass.
Seriously though, this got me thinking about error messages- what kinds of error messages I find useful and what sort of error messages I hate. Obviously I hate messages like "unknown abstract schema type". Worse still is the *absence* of error messages (aka, the "silent failure") and only slightly better is the NullPointerException (or any other exception with a broad, generic name) with the stack trace.
So what makes a good error message good and a bad error message bad? I have narrowed my scope to four things: Context, Clarity, Detail and Action-ability.
- Context: this error didn't just happen out of the blue. What was the program doing when the error occurred? Was it trying to locate a class defined in persistence.xml? Was it trying to find META-INF/persistence.xml on the classpath? Was it attempting a network connection? A disk write? Was this action part of some larger task? Just what was going on in the system when the error occurred?
- Clarity: errors should be concise, plain-English descriptions of the fault condition that give the developer (or the end user) a concrete idea of what actually went wrong. "Unknown abstract schema type" doesn't do that. "Unable to find a <class> entry for [Classname] in persistence.xml" would give a much clearer idea of what went wrong.
- Detail: hand-in-hand with clarity, the error must give enough detail so that the problem can be resolved, but not so much detail that the user or developer is buried in irrelevant minutia. "Class not found" isn't enough detail. "Unable to locate class net.spatula.news.GoatCheese on the classpath" is better. Along with Clarity, "Unable to locate class net.spatula.news.GoatCheese during initialization" is better still.
- Action-ability: this goes beyond simply giving an error message to which the developer or end-user can respond with some action; it's better still when the error message can offer suggestions. I was pleased to see this show up in PostgreSQL 8.1. Not only did it tell me what my error was, it offered a HINT about what I might do to correct it. How much easier my morning might have been had Glassfish Persistence said, "Could not find an entry for class [Article] while initializing mappings. Make sure the class is specified by EJB or by a <class> entry in META-INF/persistence.xml."
Yeah, it's a little more work for the framework developer, but it makes the framework a lot more usable and developer-friendly.
Labels: software
Wednesday, November 01, 2006
The Disintegration of Persistence?
I would try to figure out what the hell happened, but it's been a long day already. I think I'll just download and unpack Dali again and re-install it.
Weird.
Update: reinstalling didn't work. The files are all there, but Eclipse simply refuses to see or read them. My project is dead in the water until I can figure out what the hell is going on.
Update #2: it looks like somehow Eclipse also lost WTP, which is a dependency for Dali. No idea how it could have lost WTP either.
Update #3: was it something I upgraded? *burp* Indeed, when the Dali page says that it is targeted against a particular release of WTP, it really means it. The natural thing for a programmer geek to want to do is to upgrade plugins and packages to their newest, most stable releases, but it turns out that one of the upgrades I had done broke Dali. It's a bit frustrating that even with -debug -console -consoleLog -vm {path to java.exe}, the plugin failed *silently*.
The fix is to unzip the targeted version of WTP and use that only. The all-in-one bundle includes Eclipse as well, so don't even bother grabbing Eclipse first. Dali is apparently so fragile still that it is targeted against one particular build of the Eclipse SDK. Once WTP all-in-one is installed, then unzip Dali to get the plugin.
I look forward to the day when: Dali works with the latest stable versions of things and plugins don't fail silently.
Labels: software
Subscribe to Posts [Atom]