|
Any good debater knows that you can't be prepared to defend your own argument unless you know the pros and cons of your opponent's argument. Likewise, as an architect, you can't realistically recommend one platform over another without having learned everything you can both about the platform you selected and the platform you rejected. One of my pet peeves involves people who kneejerk respond to subjective (and naive) questions like, "Which platform is better?" with their favorite platform, followed by a littany of reasons why the other platform sucks. The problem is, their reasons are usually full of crap and you end up listening to someone who sounds like a preschooler talking about why "Girls have cooties". Replace "Girls" with "Microsoft" and you get the crowd of MS-bashers. Replace "girls" with "Java" and you get a large number of C# progammers who are zealous in their belief that C# is the best language and .NET is the best platform on the planet for all applications and all requirements.
The truth is more fuzzy. Certain platforms are better at some things than others. XAML makes declarative UI building easier than Interface Builder, but IB makes loosely coupling the components in an MVC style app much easier than WPF. Can you actually say "XX is better" ? No, it has to be qualified.
So I was looking back at my RESTful samples the other day and I realized that I'd done some samples on WCF-based services, both app-hosted and IIS-hosted. I'd also done a sample on how to turn an MVC controller into a RESTful service and it's view into a declarative POX rendering. I noticed something missing - no Java!
So here it is folks, with or without popular demand: a quick cliff notes tour through how to do a RESTful Web Service in Java, from the perspective of an open-minded .NET addict.
The first thing that confused me in this endeavor was the amount of choice. In .NET, for nearly every task you can imagine, you can either use the built-in Microsoft frameworks, or there is an extremely short list of stand-out implementations and everyone seems to know what the defacto standards are. Not so with Java. Apparently there are at least a hojillion stacks for doing Web Services... so I went looking for the one that had the simplest-looking syntax and was the most familiar to me as a WCF developer. I ended up finding JAX-WS... which I guess is part of the standard Java stack as of SE 1.6, I didn't need to go to some open source site, download the source code, then spend the next 4 hours figuring out how to compile it before I even get to play with it. IMHO, that practice is like triple-wrapping Christmas presents in duct tape: If you want people to use your stuff, at least make it easy to unwrap dammit!
So here I am with my decision to use JAX-WS. First thing I needed to do was define the service contract. I realize that I'm using REST, but the way in which data is serialized onto the wire in XML is part of the contract. There may be no WSDL, but even with RESTful POX you need to agree on what your payloads look like. So here's a sample Java class that uses some annotations to inform JAXB how it gets serialized as XML (yes, I am modeling a hamburger service).
package com.dotnetaddict.blog;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAttribute;
@XmlRootElement
public class Hamburger {
@XmlAttribute
public int calories;
@XmlAttribute
public String burgerName;
}
Now that I've got my individual burger type defined, I needed to define the burger collection type, which is basically just a wrapper so that I can define the XML element name for the root output of the service:
package com.dotnetaddict.blog;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlElement;
@XmlRootElement(name="Burgers")
public class BurgerList {
@XmlElement(name="Burger")
public final List<Burger> burgerList = new ArrayList<Burger>();
}
If you've ever used the .NET attributes for providing hints to the XML serializer, then you'll notice that these attributes are nearly identical in form, function, and even in name.
Now we need to create an implementation of a provider for the endpoint. This is basically a class that has methods on it that respond to HTTP messages. You write your own quick dispatcher based on the method (GET, PUT, POST, DELETE) and then inside each method handler, you examine the path (e.g. "/allburgers" or "/lowfatburgers" etc) and perform the appropriate action. This is where the pattern deviates a little from the .NET world. With WCF, you simply provide a UriTemplate annotation, er, attribute, on the top of each method in your service. This attribute tells WCF what URL pattern is handled by the method below, and whether it handles GET, PUT, POST, or DELETE. This attribute also automatically chunks parameters out of the URL path and converts them into method parameters. So, in this manner, the main difference between JAX-WS and WCF is that with JAX-WS, you are writing your own dispatching and URL parsing logic. For a standard URL without much complexity, this means the difference of about 15-20 lines of code.
So, here's a stubbed out endpoint implementation:
package com.dotnetaddict.blog;
// imports go here...
@WebServiceProvider
@ServiceMode(value=Service.Mode.MESSAGE)
public class BurgerEndpoint implements Provider<Source> {
@Resource
private WebServiceContext wsContext;
private BurgerList allBurgers = new BurgerList();
private JAXBContext jc;
public BurgerEndpoint()
{
try {
jc = JAXBContext.newInstance(BurgerList.class);
} catch (JAXBException je)
{
throw new WebServiceException("Cannot create JAXBContext", je);
}
// somehow get list of burgers.. could be from database, or here it's just fake.
Burger b = new Burger();
b.calories = 1000;
b.burgerName = "Teh Woppa";
burgerList.Add(b);
}
public Source invoke(Source source)
{
MessageContext mc = wsContext.getMessageContext();
String path = (String)mc.get(MessageContext.PATH_INFO);
String method = (String)mc.get(MessageContext.HTTP_REQUEST_METHOD);
System.out.println("[Burgers] HTTP " + method + " request for " + path);
try {
if (method.equals("GET"))
return get(source,mc);
if (method.equals("POST"))
return post(source,mc);
if (method.equals("PUT"))
return put(source, mc);
if (method.equals("DELETE"))
return delete(source, mc);
} catch (JAXBException je)
{
throw new WebServiceException(je);
}
throw new WebServiceException("Unsupported HTTP Method "+method);
}
private Source get(Source source, MessageContext mc) throws JAXBException
{
String path = (String)mc.get(MessageContext.PATH_INFO);
if (path != null && path.equals("/all"))
return getAllBurgers();
if (path != null && path.equals("/lowfat"))
return getLowfatBurgers();
return null;
}
private synchronized Source getAllBurgers() throws JAXBException {
return new JAXBSource(jc, burgerList);
}
private synchronized Source getLowfatBurgers() throws JAXBException {
return new JAXBSource(jc, burgerList); // fool the public!
}
// put, delete, post implementations
}
So now we have our serialization object placeholders and we've got our endpoint implementation. The next thing we need to do is publish the service. We can do that by either publishing to Tomcat, Glassfish, or some other container... or we can just self-host it in a manner that is VERY (some would say uncannily, perhaps eerily) similar to the way WCF does it.
package com.dotnetaddict.blog;
import javax.xml.ws.*;
import javax.xml.ws.http.*;
public class ServicePublisher {
public static void main(String[] args) throws Exception {
String burgerAddress = "http://localhost:6666/burgers";
Endpoint burgerEndpoint = Endpoint.create(HTTPBinding.HTTP_BINDING, new BurgerEndpoint());
burgerEndpoint.publish(burgerAddress);
// This is just a hack so we don't stop hosting. I took this from some sample code I found somewhere...
// I'm not a Java guy so I don't know what would be better to put here..
Thread.sleep(10000000L);
}
}
And so that's that. A RESTful (however trivial) Web Service implemented in Java. The big take-aways for me were:
"One of my pet peeves involves people who kneejerk respond to subjective
(and naive) questions like, "Which platform is better?" with their favorite
platform, followed by a littany of reasons why the other platform sucks."
...
"It's just about as easy to make a RESTful Web Service in Java (if you pick
the RIGHT stack... there are others that suck)"
Was wondering if anyone was going to catch that one. By "others that suck"
I am referring to the relative complexity of code required to produce the
same result. I looked at Axis 2.0 and it seemed like a perfectly decent
stack, but I found the code for a standard RESTful web service to be less
easy to read and much less easy to infer URL format. I'll re-word the blog
post so that instead of "suck" it mentions the one objective point about
relative code complexity and size.
Want to do it one more time in Rails? :) Would seem pretty straight forward
too.