If anyone asked what the biggest new "geek functionality" for applications in 2005 was, you'd find tagging somewhere in the top three (RSS/ATOM and AJAX being the other two) so I knew it wouldn't be long till I thought about adding support for it in my own applications.
I actually started thinking about how I wanted to use tagging in $WORK_PROJECT months and months ago, wrote it down in my notebook, and promptly forgot about it. The area of functionality I wan't to replace with a tagging system was smelly as hell, at the time of writing it I did something "stupid" as a way of avoiding adding 4-5 additional fields to a table. It worked in the immediate sense, but started to go moldy quite quicly.
Anyway, that stories not to be told, so back to tagging. Every so often my mind jumps back to the reworking said code to use tags and wondering the best way of doing so - from what data I want to store, how I want to query it, and use it from both a UI and code perspective. Over the weekend I started to hack out some code to see what's what in my head...
It all started with a test and some random uncompilable code which looked something like:
public class Main {
@Test
public void testTagging() {
TagManager tagManager = new TagManager();
tagManager.tag("Mark Derricutt", "name");
assert tagManager.findTagsFor("Mark Derricutt").contains("name");
assert tagManager.findObjectsTaggedWith("name").contains("Mark Derricutt");
}
}
The test failed, some code was written, the test passed, and a little girl waits.
Roll forward an hour or so, a party, some dvds, more tests and some more code and I've now extracted, abstracted, injected, and in the words of some non-java friends "deliberatly complicated" things, but now I can simple tag an object or query for a tag and the (uh oh, he's going to drop the F-bomb) framework finds the appropriate persistence handler for the objects class and delegates loading/saving instances to whatever database system you use.
So now we initialize the system with:
TagItemManager tagItemManager = new TagItemManagerImpl(); tagManager = new TagManager(tagItemManager); ListtagResolvers = new ArrayList (); tagResolvers.add(new PersonResolver()); tagManager.setTagResolvers(tagResolvers);
The default TagItemManager implementation simply stores everything in memory, and could easily be swapped for a JDBC/Hibernate based version. The above code provides a single resolving for our Person business object, tagging any other class will trigger a TagException.
public class PersonResolver extends AbstractTagResolver {
// Inject this somewhere...
private PersonDao personDao;
public String getTagType() {
return "person";
}
public boolean accepts(Class aClass) {
return aClass.equals(Person.class);
}
public Object dereferenceObject(Object o) {
return ((Person) o).getPersonId();
}
public Object resolveObject(Object object) {
Integer personId = (Integer) object;
return personDao.findPersonById(personId);
}
The TagResolver simply tells the system what class(es) it will tag and how to identify and resolve the instance (we're assuming everythings keys by Integers). Back on the code side I can tag away, or query tags happily with everything happening behind the scenes.
The question that remains is whether I'm duplicating functionality thats already provided by some F/OSS project out there, and whether what I've whipped up truely is overkill.
One thought that comes to mind is removing the TagResolver entirely, and switching to JDK5 annotations (although $WORK_PROJECT is still JDK1.4 base) which could then just decorate the value object:
@Taggable(key="personId")
public class Person {
...
}
This doesn't give any way to resolve the key back to an instance thou :(