The past month I've been using an open source project called
Dropwizard. Dropwizard is a self-described as being a “Java framework for
developing ops-friendly, high-performance, RESTful web services”. Dropwizard is an awesome piece of kit that bundles
best of breed Java tooling like Jetty,
Guava and Jersey. Speaking of Jersey, this is
what I'd like to talk about today, specifically about how Dropwizard exposes the ability to create your own Jersey
ExceptionMapper and how the built-in Dropwizard ExceptionMappers might cause you some grief, with a workaround.
What is an ExceptionMapper?
Jersey, or should I say JAX-RS, exposes a mechanism that will allow you to map a thrown
Exception or Throwable to a REST response, instead of being unhandled and being presented to
the user as some stacktrace or error text. (This mechanism requires than you implement the generic
ExceptionMapper interface and then register it.) This is excellent for REST APIs that like to
return errors back to the client as part of using the API, like returning a JSON representation
of an Exception that can be parsed and handled on the client.
Custom ExceptionMappers in Dropwizard
My initial impression of Dropwizard in the context of Jersey and needing to register custom ExceptionMappers was very
positive since Dropwizard exposes an API for registering ExceptionMappers. Here is a very brief example for those of
you looking to register your custom ExceptionMapper within Dropwizard:
package org.thoughtspark.dropwizard.app;
import org.thoughtspark.dropwizard.app.ApplicationConfiguration;
import org.thoughtspark.dropwizard.app.GenericExceptionMapper;
import com.yammer.dropwizard.Service;
import com.yammer.dropwizard.config.Bootstrap;
import com.yammer.dropwizard.config.Environment;
/**
* Example Dropwizard {@link Service}.
*/
public class ApplicationService extends Service<ApplicationConfiguration> {
/**
* Entry point for running this services in isolation via Dropwizard.
*
* @param args the arguments
*/
public static void main(String[] args) throws Exception {
new ApplicationService().run(args);
}
/**
* {@inheritDoc}
*/
@Override
public void initialize(Bootstrap<ApplicationConfiguration> bootstrap) {
bootstrap.setName("application");
}
/**
* {@inheritDoc}
*/
@Override
public void run(ApplicationConfiguration applicationConfiguration, Environment environment) throws Exception {
// Register the custom ExceptionMapper(s)
environment.addProvider(new GenericExceptionMapper());
}
}
The GenericExceptionMapper being registered will handle all Throwables thrown and return a JSON payload representing
the error and its message.
Dropwizards Secret “Gotcha”
Everything was going great until I started using Dropwizard
Validation. I noticed that whenever my bean validation
failed, instead of seeing a JSON payload of my validation exception, I was always seeing an HTML version of the
exception…almost as if I never registered my custom ExceptionMapper, or maybe my custom ExceptionMapper just wasn't
working. Seeing that all Exceptions extend Throwable, I didn't see how my ExceptionMapper wasn't configured properly
so I dropped into the debugger.
After some looking around, I see that the actual exception being throw was of type InvalidEntityException. At this
point, I created a new ExceptionMapper specifically for the InvalidEntityException, restarted Dropwizard and it
worked! Instead of the HTML responses for InvalidEntityExceptions, I saw my JSON representation. Everything was
working great…that is until I restarted the server for a different reason and I noticed that the
InvalidEntityExceptions had gone back to HTML. I knew I hadn't changed anything related to the ExceptionMapper so I
started debugging. After being unable to get the debugger to hit any break points in my ExceptionMappers I started
looking into the Dropwizard sources, thank goodness for open source software, and that is when I saw something,
Dropwizard is registering its own ExceptionMapper for the InvalidEntityException. What was still bugging me was why my
ExceptionMapper worked once and upon server restart it stopped working, without any changes to my code. Once again I
found myself in the bowels of Dropwizard's source and that's when I found my problem.
Dropwizard is adding its custom ExceptionMappers into Jersey's singletons Set, a Set that does not guarantee order.
This explains why one time my ExceptionMapper would work and another time, the built-in Dropwizard ExceptionMapper
would work. Now that we know the problem, below is one way to work around the problem:
package org.thoughtspark.dropwizard.app;
import org.thoughtspark.dropwizard.app.ApplicationConfiguration;
import org.thoughtspark.dropwizard.app.GenericExceptionMapper;
import com.fasterxml.jackson.jaxrs.json.JsonParseExceptionMapper;
import com.sun.jersey.api.core.ResourceConfig;
import com.yammer.dropwizard.Service;
import com.yammer.dropwizard.config.Bootstrap;
import com.yammer.dropwizard.config.Environment;
import com.yammer.dropwizard.jersey.InvalidEntityExceptionMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Example Dropwizard {@link Service}.
*/
public class ApplicationService extends Service<ApplicationConfiguration> {
/**
* Entry point for running this services in isolation via Dropwizard.
*
* @param args the arguments
*/
public static void main(String[] args) throws Exception {
new ApplicationService().run(args);
}
/**
* {@inheritDoc}
*/
@Override
public void initialize(Bootstrap<ApplicationConfiguration> bootstrap) {
bootstrap.setName("application");
}
/**
* {@inheritDoc}
*/
@Override
public void run(ApplicationConfiguration applicationConfiguration, Environment environment) throws Exception {
// Remove all of Dropwizard's custom ExceptionMappers
ResourceConfig jrConfig = environment.getJerseyResourceConfig();
Set<Object> dwSingletons = jrConfig.getSingletons();
List<Object> singletonsToRemove = new ArrayList<Object>();
for (Object s : dwSingletons) {
if (s instanceof ExceptionMapper && s.getClass().getName().startsWith("com.yammer.dropwizard.jersey.")) {
singletonsToRemove.add(s);
}
}
for (Object s : singletonsToRemove) {
jrConfig.getSingletons().remove(s);
}
// Register the custom ExceptionMapper(s)
environment.addProvider(new GenericExceptionMapper());
}
}
In the code above, I remove all Dropwizard ExceptionMappers so that I have complete control over how my application
renders Jersey Exceptions. Now no matter how many times I restart the server, my custom ExceptionMapper will be used
and I can always expect JSON to be returned for Exceptions thrown on the server. Of course, you might need to change
the approach above based on your needs but for this scenario, I just wanted any ExceptionMapper that Dropwizard
provided to be done away with so I could use my custom versions that returned JSON instead of HTML.
Conclusion
Dropwizard is awesome and anytime I have to write Java-based REST servers, I'll be using it. I do question the
built-in ExceptionMappers, especially with their inability to be configured to output something other than the
hardcoded HTML, but with the workaround above, I don't have to be stuck because of them. Please do not let this take
away from Dropwizard and if you get tired of having to use the workaround above, I'm sure the team would welcome a
patch…if you beat me to it.
I was going through my Google Reader stream today when I came across a thread that
bothered me: Borderlands 2’s Writer Says He’ll Change Tiny Tina If She Conveys Racism, As Some Players Think.
After reading the thread, I was shocked at the accusation myself. I remember playing Borderlands 2
with a good friend of mine a few days after the release and not only enjoying the Tiny Tina character but laughing so
hard I was crying. I love Tiny Tina and I just cannot see how she or the person behind her
(Anthony Burch), could be considered racist. I would had left it at a simple
reply to Anthony on Twitter supporting him and Gearbox Software but feeling that
there is so much more to this than 140 characters can say, I figured I'd weigh in here.
What is Racism?
Racism, as defined by Merriam-Webster, is as follows:
Any action, practice, or belief that reflects the racial worldview—the ideology that humans are divided into separate
and exclusive biological entities called races, that there is a causal link between inherited physical traits and
traits of personality, intellect, morality, and other cultural behavioral features, and that some races are innately
superior to others.
That being said, where in the description above does it say that someone of one race using the lingo of another
race is racism, or that a race could even impose ownership of lingo? The idea of saying that Tiny Tina's character
is racist because she uses “black lingo” is a joke, although not a laughing matter because it's ridiculous
accusations like this that fuel the controversal matters of racism. Could you imagine calling
Eminem, one of the most talented rappers ever to grace a microphone, a racist because he's
white and uses “black lingo” in his raps?
My Opinion
In my personal opinion, I think the idea of suggesting that lingo could be owned by a race could be considered racist.
I mean, suggesting that people of a certain race are the only ones to use certain words is a race-based stereotype, much
like what is described above. Having grown up in Georgia myself, I knew quite a few white girls/guys that used
black lingo and black girls/guys that used white lingo. To us, they were just words. As long as you stayed away
from the derogatory words/phrases commonly referred to as racial slurs, you used whatever verbiage best fit who you
were with and the context of the conversation regardless of which race used the verbiage most or coined a particular
word/phrase.
What did Gearbox say about the matter? Here is Gearbox President Randy Pitchford's
response on Twitter:
@reverendanthony tina is not racist because you are not racist. You're a pillar
of tolerance and inclusion.
In Conclusion
Borderlands 2 is an awesome game and Tiny Tina is one of my favorite game characters of all times. Like many others, I
find her hilarious and did not even think of the race card while enjoying the parts of the game she was in. I think
Anthony did an excellent job making Tiny Tina quirky, unique and memorable, all the things you'd want from a gaming
character. I also applaud his handling of the situation linked to above. In the end, I wish him the best and I hope
that people can stop trying to make something out of nothing, the world is already destructive enough without outlandish
accusations about very sensitive subjects such as racism.
Let's make it official, in case you didn't hear on Twitter,
I've signed a contract with O'Reilly Media to write a book about
Underscore.js. How did I get into this awesome situation you might ask? Well, back in
October Jeremy Ashkenas posted
on Twitter saying that if you were interested in writing
a book about Underscore.js, let him know. I submitted my proposal and it was accepted. WOOHOO!!! Needless to
say, I'm very excited about this opportunity and I'll do my best to make sure this thing happens.
Any Suggestions?
I'm not looking to have my name on the cover of just any book, I want this book to be the best possible. That being
said, I'd love to hear anything you think that could help make the book awesome. To submit your suggestions, leave
your requests/suggestions in the comments below.
Progress
I've already started work on this book based on the proposal sent to O'Reilly. As I make progress, I'll keep you guys
up to speed here on ThoughtSpark.org. Feel free to reach out to me on Twitter or
in the comments with anything pertinent to this effort.
I've decided it's time to rethink how I maintain and deploy ThoughtSpark.org. My
current deployment model is to use Drupal to craft/host my site's content and I currently
pay a small monthly fee to GoDaddy for hosting Drupal. While there isn't anything
really wrong with my current model, I've grown tired of it. Below are a few pain points worth mentioning.
Maintenance Overhead
I've grown tired of maintaining Drupal. I'm tired of applying a security patch or verision update and having my
whole site turn to crap. Why? All of my modules then need to be re-enabled and/or updated. How is this a
problem? All non-core functionality on my site (sitemap.xml generation, SPAM filtering, syntax highlighting, …)
are all enabled via modules. If these modules are disabled during the update, and they are, I now have to go through
the process of re-enabling them just so my site doesn't look like crap and I don't get SPAMed to Hell and back.
Don't get me wrong, Drupal is a phenomenal product. It's an excellent
CMS and Drupal is also a great example of what an
OSS project should be. It's not Drupal's fault that I don't need a
CMS and I'm sure there is a reason that the update process is more
painful than I'd like.
Another aspect of the maintenance overhead is the fact that Drupal runs on PHP and needs a
database, MySQL in my case, so you have to either host your server or pay someone to host it
you. I chose the second option. The overhead for this of course is the financial cost, regardless of how large or
small it is.
Authoring
The options I've been exposed to in Drupal for authoring content on my site is to use raw
HTML or using one of the
WYSIWYG editors. The problem with raw HTML is it's cumbersom and
error prone. Crafting a single post can often end up with a lot of time spent finding HTML typos. The problem with
WYSIWYG editors is that you often end up fighting them. Either the output is junk or they don't handle certain use
cases, like handling code blocks. Regardless, I loathe creating content in Drupal but again, I don't feel it's
Drupal's fault, I just want something simpler.
One approach to creating web-based content I've become very fond of as of late is
Markdown. Markdown is a great language that allows me to focus more
on the content being crafted while still being able to style my content very easily. I can even drop in raw HTML
wheenver I feel the need to. If you've ever visited any GitHub project/user homepage or
a project's wiki, you've seen the result of Markdown.
The Solution
The new ThoughtSpark.org will no longer be using Drupal and will no longer be deployed on GoDaddy. Instead, I'm going
to use a static website generator that will take my Markdown files and create my website. The tool I will be using is
Middleman and I will be using GitHub Pages as my host. Not
only will writing/maintaining my website content be easier but now it will also require no cost to host. Those two
things are good enough reasons for me to switch but there are also the following reasons that are equally compelling:
- Security: With there being no server-side component and no server-side processing, there are much fewer security
issues that I need to concern myself with
- Performance: With there being no server-side component and no server-side processing, the performance of the site
will be faster
- Deployment: The solutions for hosting static websites are plentiful and you are no longer locked into a
particular host/product for hosting your website. (There's a chance you're already using a
service that will host your static websites for you. Examples: GitHub's Pages and
DropBox are two excellent examples.)
In Closing
GitHub Pages, Markdown, Middleman and Twitter Bootstrap have made it very easy
for me to re-create and maintain ThoughtSpark.org. I feel like with this new approach for ThoughtSpark.org, I'll be
able to get posts out quicker and much easier, while saving a few bucks along the way. Thanks for your patience and I
look forward to sharing with you on my new platform.
Note: There are a few things left to finish before I'd say that the migration is complete, follow
here if you're interested.
Note: Originally I had planned on migrating all of the old Drupal posts to the new platform. I've decided against
it for a few reasons and will instead only migrate things upon request. To request such a thing, use the
issue tracker or hit me up on
Twitter.