tag:blogger.com,1999:blog-8355257Sun, 18 Jan 2015 22:36:57 +0000JavaJAX-RSJerseyRESTRESTfularchitecture of the webURIiteratorweb serviceswwwC++JSR-311distributed computingmedia typesrfc 2616user interface design303API discoveryAcceptAjaxBGGABufferedReaderContent-TypeDSLGETGWTGateway Software SymposiumGuiceHATEOASIterableJAX-WSJNIJSONJUGMac OS XMavenNo Fluff Just StuffPOSTRESTful servicesRemoteIteratorSOASOAPSmartGWTSt. Louis Java User's GroupURI fragmentWAXWS-*WSDLWall of ErasureWindowsXMLXPXcodeadaptersallocationannotationsanti-patternsapplearchitectureassertionsbad designblog-tagbugclientclosurescode couplingconflictscontainercurlcurvesdangerdependency injectiondesign patterndocumentationentity providerestimationexampleframeworksgenericsgood documentationgood service designgrizzly servlethello worldhttphttp redirecthuman factorsinterface chainingiteratorsmistakesopen sourceownershipphilanthropyprime ministerprogrammingproject managementproject planningrepresentationsresourcesresponseriskView from the FringeObservations and commentary from the edge of normal.http://viewfromthefringe.blogspot.com/noreply@blogger.com (Brian Gilstrap)Blogger61125tag:blogger.com,1999:blog-8355257.post-7398978492416080434Sat, 02 Aug 2014 01:17:00 +00002014-08-01T20:17:19.151-05:00Old meme still makes me me laughThere are many variations on this particular 'quote' in the history of the Internet (I heard it long ago, somewhere in the mid 1990's). The version I remember doesn't seem to match the recorded history we can find at sites like the Way Back Machine. I think that means this was a popular meme back then and some exercised poetic license. The version I remember is:<br /><br />"You couldn't get a clue if you stripped naked, smeared yourself with clue musk, and ran naked through a field full of horny clues at the height of clue mating season.”<br /><br />Hoping this gave you a chuckle...<br /><br />icebrghttp://viewfromthefringe.blogspot.com/2014/08/old-meme-still-makes-me-me-laugh.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-8167559356350243881Thu, 27 Jun 2013 01:12:00 +00002013-06-26T20:12:43.125-05:00Treat all gentlyTreat all gently.http://viewfromthefringe.blogspot.com/2013/06/treat-all-gently.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-707610514082445784Sun, 16 Jun 2013 23:11:00 +00002013-06-16T18:11:08.566-05:00Chaining Either's in Java 8The discussion about <a href="http://blog.jessitron.com/2013/06/whats-dirtier-than-comments-exceptions.html">Either in Scala</a> and <a href="http://heath-tech.blogspot.com/2013/06/all-smart-kids-are-writing-blog-posts.html">trying to implement it in Java</a> got me to thinking. If we make the simplifying assumption that the error portion of any Either is some kind of Exception, It would seem we can make pretty good progress in using the Either pattern of handling error conditions.<br /><br />I'd be interested to hear of problems and/or improvements to this idea.<br /><br /><script src="https://gist.github.com/anonymous/5793783.js"></script><br /><br /><br />http://viewfromthefringe.blogspot.com/2013/06/chaining-eithers-in-java-8.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-3858150461912753535Sun, 16 Jun 2013 22:52:00 +00002013-06-16T17:52:37.344-05:00Stop writing your code for the happy pathSo Jessitron recently <a href="http://blog.jessitron.com/2013/06/whats-dirtier-than-comments-exceptions.html">posted about avoiding cxceptions in Scala code using 'Either</a>'. Mario Aquino followed up with <a href="http://marioaquino.blogspot.com/2013/06/styles-of-handling-conditional-logic.html">an example of using Either in Ruby</a>, and Heath responded with a post about <a href="http://heath-tech.blogspot.com/2013/06/all-smart-kids-are-writing-blog-posts.html">the awkwardness of using Either in Java compared to using checked exceptions.</a><br /><br />I agree with all of them on many levels. But the most important thing they didn't say (but which is implicit in their discussions), is the need to stop thinking that writing code is about implementing the happy path. It always amazes me when I encounter code base and discover how little thought has been put into the error handling (the "not so happy paths"). The truth is that about 10% of software engineering is implementing the happy path. The other 90% is figuring out how things can go wrong and then eliminating those possibilities (when feasible) or writing code that gracefully handles the errors.<br /><br />The art of system design is in finding ways to simplify the code that handles all the errors that can occur. The happy path code can never get any shorter than the minimum required. It's all the error handling code that leaves room for good design.http://viewfromthefringe.blogspot.com/2013/06/stop-writing-your-code-for-happy-path.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-2436823564673276694Thu, 22 Mar 2012 02:46:00 +00002012-03-21T21:46:58.929-05:00A Checklist for Designing SoftwareSometimes when we are developing software, developers can suffer from "forest and trees" syndrome (being unable to see the forest due to all the trees in the way). We get caught up in the details, or time pressure comes to bear, etc. We then fail to remember the overarching requirements for any software system. It's not like this hasn't been written about many times, but I've distilled it down into a short list of the most common requirements in order of their priority. Sometimes, a particular application will add requirements, cause requirements to be re-ordered, or have particularly heavy weight associated with one or more of the requirements. But I find this a useful touchpoint when building, modifying, enhancing, or rebuilding just about any software. It's not exhaustive, but it helps me keep things in focus.<div><br /></div><div>In typical priority order:</div><div><br /></div><div><u>Does it work?</u></div><div>Our software needs to produce correct outputs or at least flag when it suspects it is failing to produce correct outputs (it's amazing to me how often developers put so much thought into the "happy path" and almost none into detecting and handling errors).<br /></div><div><u>Is it secure enough?</u></div><div>Sometimes this is a noop. Sometimes it's one of the most crucial considerations (a cryptographic algorithm being the poster child here). And security is much harder to add after the design is mostly complete. It's best to get on top of this one as early as possible, as security requirements that clash with a system design can cause problems in every other category.</div><div><br /><u>Does it meet our scaling needs?</u></div><div>If our software does what we want but can't handle the required concurrent requests or total volume of requests quickly enough, it is effectively non-functional. Often back-of-the-napkin estimates are good enough to understand the basic problem here. Are you dealing with tens, thousands, millions, or billions?</div><div><br /><u>Is it as simple as possible?</u></div><div>The smaller and simpler the code is, the easier it is to wrap your head around it and the easier it is to troubleshoot. Also,&nbsp;the less you might have to rewrite to meet a new or altered requirement,. This isn't an excuse for skipping error checking/handling, logging, security etc. Rather, we should implement those things as simply as possible. Sometimes, this may mean using a framework that helps solve part of the problem (e.g. an dependency injection container). Sometimes, it means <b>not</b>&nbsp;using a framework because that framework is overkill for the problem, overly difficult to use, or too opaque.</div>http://viewfromthefringe.blogspot.com/2012/03/checklist-for-designing-software.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-8518770840207395222Thu, 10 Mar 2011 15:18:00 +00002011-03-10T09:18:40.747-06:00mistakesprime ministertwitterPardon me, but I believe there's been a mistake...I logged into my Twitter account this morning after having not looked at it for quite some time. Imagine my surprise when I looked at the list of people following me and I see this:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://lh5.googleusercontent.com/-rZFawdjoGk4/TXjrLyE50pI/AAAAAAAAAIA/C7gIIY10e7o/s1600/UNPrimeMinisterFollower.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://lh5.googleusercontent.com/-rZFawdjoGk4/TXjrLyE50pI/AAAAAAAAAIA/C7gIIY10e7o/s1600/UNPrimeMinisterFollower.png" /></a></div><br />While it's flattering to entertain the idea that the UK Prime Minister cares about my tweets, somehow I don't think so.<br /><br />I've gotten my unintended humor for the day and the day has barely begun...http://viewfromthefringe.blogspot.com/2011/03/pardon-me-but-i-believe-theres-been.htmlnoreply@blogger.com (Brian Gilstrap)1tag:blogger.com,1999:blog-8355257.post-1975315756846682378Tue, 01 Mar 2011 17:53:00 +00002011-03-01T11:53:20.779-06:00Jargon, REST, and ReuseBenjamin Carlyle has <a href="http://soundadvice.id.au/blog/2011/02/09/">a great post</a> about jargon in REST and how it relates to media types and reuse. RESTs uniform API is great but he succinctly makes a compelling case for why we also need to focus on reusing media types whenever possible to really enable reuse across services and over time.<br /><br />It's well worth your time. Go <a href="http://soundadvice.id.au/blog/2011/02/09/">read it now</a>.http://viewfromthefringe.blogspot.com/2011/03/jargon-rest-and-reuse.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-4861527105252430280Wed, 16 Jun 2010 02:22:00 +00002010-06-15T21:22:29.608-05:00AcceptconflictsContent-Typemedia typesRESTRESTful servicesrfc 2616Handling conflicts in media type headersIf you read my <a href="http://viewfromthefringe.blogspot.com/2010/06/versioning-rest-services.html">previous entry discussing versioning of RESTful services</a>, I pointed out how we can leverage media types to support incompatible versioning of RESTful services. I think this is an excellent solution, but it can lead to an issue for service implementors<br /><br />Imagine you have a service which has more than one version. Let's say it's a hotel reservation system, just to pick something familiar. We've picked media types for our versions of the service, and adopted the convention that in the absence of any media types we will use "Version 1" of the service. Assume our media types are "application/hotel-json" and "application/hotel-json-v2".<br /><br />If you support operations for updating a reservation using PUT, you can run into a situation where you have both a Content-Type header (specifying the content of the entity body being used to update the reservation) and an Accept header specifying the kind or kinds of entities the client can accept in the response. This might be just fine, if the two versions of the service can be intermixed between the sent entity and the returned entity. But the two versions of the service might not mix. For example, an update sent using version one of the updating entity might not contain a value required for version two of the service.<br /><br />In this situation what the client did is an error, so we clearly need to return a status code in the 400 range. But which is the most appropriate code? A quick look at the 4XX codes shows two promising candidates:<br /><br />406 - Not Acceptable<br />415 - Unsupported Media Type<br /><br />But when we read the description of 415 in <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a> we see it isn't appropriate:<br /><br /><blockquote>The server is refusing to service the request because the entity of&nbsp;the request is in a format not supported by the requested resource&nbsp;for the requested method.</blockquote>The Accept header is specifying a media type the service understands for the resource, it's just the combination of provided entity media type and requested return media type that is a problem. Happily, status code 406 fits perfectly:<br /><br /><blockquote>The resource identified by the request is only capable of generating&nbsp;response entities which have content characteristics not acceptable&nbsp;according to the accept headers sent in the request.</blockquote>Interestingly, the RFC makes a comment about this very situation, and suggests that returning an entity type different than those specified in the Accept header might be preferable to a 406:<br /><br /><blockquote>Note: HTTP/1.1 servers are allowed to return responses which are&nbsp;not acceptable according to the accept headers sent in the&nbsp;request. In some cases, this may even be preferable to sending a&nbsp;406 response. User agents are encouraged to inspect the headers of&nbsp;an incoming response to determine if it is acceptable.</blockquote>In this particular scenario (where a required value isn't provided), one way to return a result would be to "downgrade" to a version 1 response in hopes that works for the client. There are potential issues with this, if the semantics of applying a version 1 update differ from those that would have occurred with a version 2 update. And if the client can't interpret the returned version 1 entity, they may end up in an inconsistent state. But it's a good idea to keep in mind and make work whenever possible.http://viewfromthefringe.blogspot.com/2010/06/handling-conflicts-in-media-type.htmlnoreply@blogger.com (Brian Gilstrap)1tag:blogger.com,1999:blog-8355257.post-6687752607012184601Tue, 15 Jun 2010 15:04:00 +00002010-06-15T11:27:06.487-05:00media typesRESTRESTfulversioningweb servicesVersioning REST servicesI read a <a href="http://wisdomofganesh.blogspot.com/2010/06/does-rest-need-versioning.html">blog entry</a> yesterday from <a href="http://wisdomofganesh.blogspot.com/">Ganesh</a> musing on whether RESTful web services need versioning. In his posting, Ganesh suggests putting some kind of version number into the body of a request. He suggests that this be done even for a GET or DELETE.<br /><br />While I suppose this could work, it feels awkward. Putting a version number into an entity body for a GET, which would typically not have an entity body, is awkward and a fair bit of work. Things get even more complicated in the case of a PUT or POST where the entity body is some binary format like an image format. In this case, the Content-Type can't just be image/jpeg (for example) since your putting some kind of version information before the image data.<br /><br />Even in the case of textual data such as XML, putting version information into the entity body requires that our XML format support a version number somewhere (probably as an attribute of the top-level element in the entity body?). This may be feasible, but it may not be feasible if we are trying to use a data format that is already defined. We could start hacking and just throw a line at the beginning of the entity body that indicates the version with the understanding that the 'normal' payload starts after this line. But this is hackish, and makes our Content-Type delcarations wrong (we aren't adhering to the content type definition since we've thrown that version information at the front).<br /><br />After reading Ganesh's entry, I thought about versioning for a while and it seemed to me that the right way to handle versioning is via media types (using, for example, the Accept and Content-Type headers). This works well because the first version of our service can be delivered when no media-type headers are specified. Then, if a client wants to take advantage of a newer, incompatible version of our service, they simply specify this using media types in the Accept and Content-Type headers.<br /><br />I could spend a fair bit of time describing this in more detail, but I did some googling and found a <a href="http://barelyenough.org/blog/2008/05/versioning-rest-web-services/">great blog entry</a>&nbsp;(written back in May of 2008) by <a href="http://barelyenough.org/blog/">Peter Williams</a> where he describes things concisely and clearly. It's short and sweet and <a href="http://barelyenough.org/blog/2008/05/versioning-rest-web-services/">well worth a read</a>.<br /><br />Back from reading his posting? There are two issues I thought of which Peter didn't directly address. One is specific to versioning, and one is a more general issue that can arise. The versioning related issue is this:<br /><br />What if your service model changes to much that the set of resources you expose has to remove one of the resources in your original version of the service?<br /><br />In this case, you make operations to that URI path with an Accept header and/or Content-Type header for later versions of your service return 404 (or similar error code) since that type of resource doesn't exist with that version of the service. While Peter didn't talk specifically about this, this approach meshes well with his description and I suspect it is the same answer he would give if asked.<br /><br />A related but more general problem can occur if your service receives a request with both an Accept and a Content-Type header and the values are incompatible. I'll address that in my next entry.<br /><br />[Updated 2010-06-15 11:25 a.m.]<br /><br />I should mention that you shouldn't create new media types unless you have to. If your service deals with ATOM or RSS, use the version number for those to distinguish different versions of your service if at all possible. In his thesis, Roy Fielding states that creating new media types is not RESTful. And that is true in the sense that custom media types reduce the likelihood that clients will know how to interpret your representations. But there is a tension here between accurately representing incompatible versions of a service and using a pre-existing media type. There's no perfect answer, just a continuum with different trade-offs for different kinds of applications.http://viewfromthefringe.blogspot.com/2010/06/versioning-rest-services.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-6207096546001374454Mon, 31 May 2010 23:51:00 +00002010-05-31T18:52:35.556-05:00API discoveryHATEOASRESTI spoke at the Gateway Software Symposium (NFJS St. Louis) last weekend. After my <a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17649">RESTful Web Services with JAX-RS talk</a>, one of the attendees asked me how to discover the proper URLs when starting to interact with a RESTful service. The impetus for the question came from my statement that many RESTful services don't have large specifications (such as the WSDL files for "Big" web services).<br /><br />I explained that well-designed RESTful services are "well-connected". By this, I mean they adhere to the idea of "hypermedia as the engine of application state", or <a href="http://en.wikipedia.org/wiki/HATEOAS">HATEOAS</a>. If your service adheres to HATEOAS, your service is accessible and navigable given just a single URL. How can this be?<br /><br />Recall that in RESTful services, we use URIs/URLs to connect various related resources. To be well-connected (I'll use this term in place of to HATEOAS mostly because the acronym looks odd to some people) and be RESTful, representations of resources should contain URIs/URLs to related resources. To make this clearer, let's look at a specific example. As we look at this example, we'll make note of the aspects of the API which couldn't be discovered by examining the results of searches or resource retrieval.<br /><br />In my talks on JAX-RS, I use a simple music service. In this service, each artist has a name and some number of music albums they have published. Each album as a name and some number of tracks on that album. Here is a simple UML diagram of the objects:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/_nyDSKzPKawU/S_6yFQ-_FWI/AAAAAAAAAEo/sFihUtj-05s/s1600/MusicServiceUML.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/_nyDSKzPKawU/S_6yFQ-_FWI/AAAAAAAAAEo/sFihUtj-05s/s320/MusicServiceUML.jpg" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">We now come to the first piece of information we can't discover for ourselves. We need to know a starting URL. With the example service, it's simply a host name and a port, as in: "http://localhost:3131".</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">When we run the music service and hit it with a browser, we see a spartan web page:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div style="text-align: center;"><a href="http://1.bp.blogspot.com/_nyDSKzPKawU/TAQ4Eq9Iz_I/AAAAAAAAAEw/2Q8__Qu1Sxo/s1600/Service+Entry+Form.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="230" src="http://1.bp.blogspot.com/_nyDSKzPKawU/TAQ4Eq9Iz_I/AAAAAAAAAEw/2Q8__Qu1Sxo/s400/Service+Entry+Form.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">While this certainly allows us to do some (mildly) interesting things, how does it help us discover the API for this RESTful service? The answer lies in examining the underlying HTML. When we do that, we see this:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><a href="http://2.bp.blogspot.com/_nyDSKzPKawU/TARHKk1R5HI/AAAAAAAAAFY/xun98ZsK4yQ/s1600/HTML+for+service+entry+page.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="http://2.bp.blogspot.com/_nyDSKzPKawU/TARHKk1R5HI/AAAAAAAAAFY/xun98ZsK4yQ/s640/HTML+for+service+entry+page.png" width="569" /></a><br /><div class="separator" style="clear: both; text-align: left;"><br /></div>At first, there might not seem to be much here. But let's look at the form for searching for artists:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/_nyDSKzPKawU/TARHYgHRsDI/AAAAAAAAAFg/bkAIbY7cr3s/s1600/Search+For+Artists+HTML+Form.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="168" src="http://2.bp.blogspot.com/_nyDSKzPKawU/TARHYgHRsDI/AAAAAAAAAFg/bkAIbY7cr3s/s640/Search+For+Artists+HTML+Form.png" width="640" /></a></div><br /><br />From this we can see that we can search for artists whose name "is" (IS), "starts with" (STARTS), "contains" (CONTAINS), or "ends with" (ENDS) the text specified by 'searchString'. If you don't live and work with HTML forms and their resulting queries every day, this still might not be obvious. So we can always go ahead and do a search. Let's search for artists whose name begins with 'F':<br /><blockquote><a href="http://2.bp.blogspot.com/_nyDSKzPKawU/TAQ5ZLnXLWI/AAAAAAAAAE4/W_xmI6t-nmw/s1600/Search+For+Artists+Starting+with+F.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><span class="Apple-style-span" style="color: black;"><span class="Apple-style-span" style="text-decoration: none;"><img border="0" src="http://2.bp.blogspot.com/_nyDSKzPKawU/TAQ5ZLnXLWI/AAAAAAAAAE4/W_xmI6t-nmw/s320/Search+For+Artists+Starting+with+F.png" /></span></span></a></blockquote>Clicking the 'Search' button does two things for us:<br /><br />1) It performs the search and gives us the results:<br /><br /><blockquote><a href="http://1.bp.blogspot.com/_nyDSKzPKawU/TAQ6N7RjozI/AAAAAAAAAFA/Z_rmoXnjBjs/s1600/Artists+Whose+Name+Starts+with+F.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="217" src="http://1.bp.blogspot.com/_nyDSKzPKawU/TAQ6N7RjozI/AAAAAAAAAFA/Z_rmoXnjBjs/s400/Artists+Whose+Name+Starts+with+F.png" width="400" /></a></blockquote><br />2) It shows us the proper format for executing queries against the service (in the browser's address text box):<br /><br /><a href="http://2.bp.blogspot.com/_nyDSKzPKawU/TAQ6rx0s7UI/AAAAAAAAAFI/1F83rN6sdwM/s1600/Query+String+for+Artists+Whose+Name+Starts+with+F.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="32" src="http://2.bp.blogspot.com/_nyDSKzPKawU/TAQ6rx0s7UI/AAAAAAAAAFI/1F83rN6sdwM/s640/Query+String+for+Artists+Whose+Name+Starts+with+F.png" width="640" /></a><br /><br /><br />Looking at this, we know that we can query for artists by specifying a <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">searchType</span> and a <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">searchString</span> if we search under <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">/artists</span>.<br /><br />My example music service supports more than HTML for representations. It also supports an XML format and a JSON format. In order to use the XML format, we need to specify that we accept a content type of "application/music-xml" (a content type I made up for this example). This is the second piece of information the service itself did not tell us. However, we could simply query the service and get back an HTML representation of the search results so it's not strictly necessary. The XML format is a more compact representation than the HTML, but the same information is contained in the HTML version.<br /><br />If we use the curl command line utility to query:<br /><br /><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">curl --header "Accept: application/music-xml" "http://localhost:3131/artists?searchType=STARTS&amp;searchString=F"</span><br /><br />We get XML results:<br /><br /><br /><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/_nyDSKzPKawU/TARLT4ioq-I/AAAAAAAAAGA/iT66rGlCNz8/s1600/XML+for+Artists+starting+with+F.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="196" src="http://2.bp.blogspot.com/_nyDSKzPKawU/TARLT4ioq-I/AAAAAAAAAGA/iT66rGlCNz8/s640/XML+for+Artists+starting+with+F.png" width="640" /></a></div><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><br /></span><br /><br /><br />Because this service is designed with API discovery in mind, we see that it reminds us of the search that was performed:<br /><br /><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">&lt; artists uri="/artists?" searchString="F" searchType="STARTS"&gt;</span><br /><br />In these results we also see an example of how to build well-connected services. Notice that each search result lists not only the name of the artist found, but also the URI of the resource we would need to retrieve in order to get a representation for that artist. For example, the artist "Fish" lists a URI of&nbsp;<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">/artists/id/149</span>. If we go to the music service in the browser and query for this URI (a URL of <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">http://localhost:3131/artists/id/149</span>), we get information about the artist named Fish:<br /><div><div><span class="Apple-style-span" style="font-family: Times, Courier, monospace;"><span class="Apple-style-span" style="font-family: Times;"></span></span><br /><span class="Apple-style-span" style="font-family: Times, Courier, monospace;"><span class="Apple-style-span" style="font-family: Times;"><blockquote><span class="Apple-style-span" style="color: black;"><span class="Apple-style-span" style="text-decoration: none;"><a href="http://3.bp.blogspot.com/_nyDSKzPKawU/TARCEYi7lnI/AAAAAAAAAFQ/PQA9tYhvzR0/s1600/Information+about+the+Artist+named+Fish.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="212" src="http://3.bp.blogspot.com/_nyDSKzPKawU/TARCEYi7lnI/AAAAAAAAAFQ/PQA9tYhvzR0/s400/Information+about+the+Artist+named+Fish.png" width="400" /></a></span></span></blockquote>Looking at the HTML, we see:<br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/_nyDSKzPKawU/TARKjJChb7I/AAAAAAAAAFo/6ZoF_BqyC_I/s1600/HTML+for+Artist+named+Fish.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="452" src="http://4.bp.blogspot.com/_nyDSKzPKawU/TARKjJChb7I/AAAAAAAAAFo/6ZoF_BqyC_I/s640/HTML+for+Artist+named+Fish.png" width="640" /></a></div><br />And if we query for the XML representation using curl:<br /><br /><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">curl --header "Accept: application/music-xml" "http://localhost:3131/artists/id/149"</span><br /><br />We see:<br /><br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/_nyDSKzPKawU/TARLN3Lv6DI/AAAAAAAAAF4/3UHpJV6FJLs/s1600/XML+for+Artist+named+Fish.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="124" src="http://1.bp.blogspot.com/_nyDSKzPKawU/TARLN3Lv6DI/AAAAAAAAAF4/3UHpJV6FJLs/s640/XML+for+Artist+named+Fish.png" width="640" /></a></div></span></span><br />Notice that the information about each album contains a URI we can use to retrieve a representation of that album, just like the search results listed a URI for each artist that was found. These are examples of how we build a well-connected RESTful service. By making sure that each HTML page (or XML result) contains URIs for related resources, users of our services can discover the proper URIs/URLs to use when invoking our service without a large specification.<br /><br />In part two, I'll explore this notion further, looking at how our self-describing service allows us to discover how to create and delete resources as well as search for and retrieve them.</div></div>http://viewfromthefringe.blogspot.com/2010/05/i-spoke-at-gateway-software-symposium.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-8193142063208125069Sat, 22 May 2010 19:51:00 +00002010-05-22T14:57:01.382-05:00Gateway Software SymposiumJavaNo Fluff Just StuffGateway Software Symposium 2010 (NFJS St. Louis)I'm attending (and speaking at) the <a href="http://www.nofluffjuststuff.com/home/main">NFJS</a> conference here in <a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/home">St. Louis this weekend</a>. The quality of the talks is excellent, and the speakers clearly know their stuff.<br /><br />The two talks I'll be delivering on Sunday are:<br /><br /><ul><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17669">Open Source Java Performance Tuning</a></li><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17649">RESTful Web Services with JAX-RS</a></li></ul><br />I hope if you're attending the conference you come to the talks to learn and participate.<br /><br />Some of my fellow OCI-ers are also presenting:<br /><br /><ul><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17685">Using Test Doubles with Mockito</a> - <a href="http://blog.james-carr.org/">James Carr</a></li><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17683">First-Class Builds with Gradle</a> - <a href="http://codetojoy.blogspot.com/">Michael Easter</a></li><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=18611">Clojure - Functional Programming for the JVM</a> - <a href="http://java.ociweb.com/mark/">Mark Volkmann</a></li></ul><br />Sessions I've attended include:<br /><br /><ul><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17610">Open Source Debugging Tools for java</a>&nbsp;- <a href="http://ambientideas.com/">Matthew McCullough</a></li><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17608">The Busy Java Developer's Guide to Advanced Collections</a>&nbsp;- <a href="http://www.tedneward.com/">Ted Neward</a></li><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17565">Software Architecture for the Cloud</a> -&nbsp;<a href="http://www.michaelnygard.com/">Michael Nygard</a></li><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17571">Programming Scala</a> -&nbsp;<a href="http://www.agiledeveloper.com/">Venkat Subramaniam</a></li><li><a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17618">Emergent Design</a> - <a href="http://www.nealford.com/">Neal Ford</a></li><li>The Busy Java Developer's Guide to Concurrency <a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17626">Part 1</a> and <a href="http://www.nofluffjuststuff.com/conference/st_louis/2010/05/session?id=17627">Part 2</a>&nbsp;-&nbsp;<a href="http://www.tedneward.com/">Ted Neward</a></li></ul><div>All good stuff.</div>http://viewfromthefringe.blogspot.com/2010/05/gateway-software-symposium-2010-nfjs-st.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-8981127664498163169Sun, 28 Mar 2010 14:56:00 +00002010-03-28T09:56:19.078-05:00architecture of the webURIURI fragmentURI FragmentsI learned something interesting when reading the Architecture of the World Wide Web, Volume One. It turns out that URI fragments (the part of a URI after a '#' character) are not interpreted as part of a URI:<br /><br /><blockquote>Note that the HTML implementation in Emma's browser did not need to understand the syntax or semantics of the SVG fragment (nor does the SVG implementation have to understand HTML, WebCGM, RDF ... fragment syntax or semantics; it merely had to recognize the # delimiter from the URI syntax [URI] and remove the fragment when accessing the resource). This orthogonality (§5.1) is an important feature of Web architecture; it is what enabled Emma's browser to provide a useful service without requiring an upgrade.<br /><br />The semantics of a fragment identifier are defined by the set of representations that might result from a retrieval action on the primary resource. The fragment's format and resolution are therefore dependent on the type of a potentially retrieved representation, even though such a retrieval is only performed if the URI is dereferenced. If no such representation exists, then the semantics of the fragment are considered unknown and, effectively, unconstrained. Fragment identifier semantics are orthogonal to URI schemes and thus cannot be redefined by URI scheme specifications.<br /><br />Interpretation of the fragment identifier is performed solely by the agent that dereferences a URI; the fragment identifier is not passed to other systems during the process of retrieval. This means that some intermediaries in Web architecture (such as proxies) have no interaction with fragment identifiers and that redirection (in HTTP [RFC2616], for example) does not account for fragments.</blockquote><br />While I had an intuitive understanding of how browsers work with URI fragments in HTML documents (retrieve the document, find the fragment, display the document starting at the fragment), I hadn't considered the semantic split, nor the implications of URI fragments being applied to other kinds of representations.<br /><br />I find the handling of fragments interesting in a number of ways. For one thing, it means that as new content types become part of the web, the creators of those content types are free to map URI fragments into that content type. So in HTML the format of URI fragments is generally a textual name that appears in the html. But in a 3D modeling format, it might take the form of [position,orientation,scale] to define a location from which the model is being viewed, the direction the camera is facing, and the scaling factor. That's nice because it allows URI fragments to be structured in a manner most appropriate for the kind of representation being retrieved.<br /><br />One possibly surprising consequence of this split is that URI fragments are not considered in URI resolution activities such as interacting with proxies or redirection. It also means that you shouldn't try to use URI's with fragments as if they represented actual resources, since the web isn't allowed to cache individual fragments and no semantic interpretation is allowed from the '#' character onward.http://viewfromthefringe.blogspot.com/2010/03/uri-fragments.htmlnoreply@blogger.com (Brian Gilstrap)5tag:blogger.com,1999:blog-8355257.post-3931168758604277490Thu, 25 Mar 2010 04:42:00 +00002010-03-24T23:42:45.201-05:00architecture of the webgood service designhttprfc 2616safe methodsWhy you should care about adhering to the Architecture of the World Wide WebI was reading more of the <a href="http://www.w3.org/TR/webarch/">Architecture of the World Wide Web, Volume One</a>", and&nbsp;<a href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a> and the definition of "safe" methods:<br /><br /><blockquote>9.1.1 Safe Methods</blockquote><blockquote>Implementors should be aware that the software represents the user in&nbsp;their interactions over the Internet, and should be careful to allow&nbsp;the user to be aware of any actions they might take which may have an&nbsp;unexpected significance to themselves or others.</blockquote><blockquote>In particular, the convention has been established that the GET and&nbsp;HEAD methods SHOULD NOT have the significance of taking an action&nbsp;other than retrieval. These methods ought to be considered "safe".&nbsp;This allows user agents to represent other methods, such as POST, PUT&nbsp;and DELETE, in a special way, so that the user is made aware of the&nbsp;fact that a possibly unsafe action is being requested.</blockquote><blockquote>Naturally, it is not possible to ensure that the server does not&nbsp;generate side-effects as a result of performing a GET request; in&nbsp;fact, some dynamic resources consider that a feature. The important&nbsp;distinction here is that the user did not request the side-effects,&nbsp;so therefore cannot be held accountable for them.</blockquote><br />This matches quite nicely with my intuition about how the web works (at least the normal web). &nbsp;That might seem like something trivial, but in fact it is something profound.<br /><br />Unfortunately, many, many web services violate the principle of safe operations. For example, they frequently have all interactions occur via GET, and use query parameters or headers or other conventions to stipulate whether the result is a resource retrieval, creation, update, or delete. This is (unfortunately) only one example of aberrant service design.<br /><br />At first, this might not seem like such a problem. But as you start to think about consuming such services or providing support for services you've built, you realize that the conventions of HTTP and the architecture of the web represent a sort of "lingua franca". Rather than making your services do more, you are making your services easier for (potential) consumers of your service to understand and use.&nbsp;If you adhere to the architecture of the web, your services don't do any more than they would have done otherwise. Rather (and this is crucial) you have implemented your services in such a way that users of your services can very easily and rapidly understand their features, capabilities, and limitations. That is a huge advantage in today's fast-moving technology world.http://viewfromthefringe.blogspot.com/2010/03/why-you-should-care-about-adhering-to.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-6858568975788768035Tue, 09 Mar 2010 05:10:00 +00002010-03-08T23:44:43.247-06:00allocationarchitecture of the webownershipURIwwwParsing "The Architecture of the World Wide Web, Volume 1"; URI allocationAs I continue to read <a href="http://www.w3.org/TR/webarch/">Architecture of the World Wide Web, Volume I</a>, I keep running into material that is completely outside of what I would have expected, yet valuable.<br /><br />For example, <a href="http://www.w3.org/TR/webarch/#uri-assignment">Section 2.2.2</a> talks about URI allocation. Since URIs are supposed to identify a single resource, it becomes important to make sure that the social organizations which allocate and assign URIs are organized so that they don't allocate the same URI to refer to more than one resource. In other words, we want to make sure that we give organizations authority to assign URIs that don't overlap, so that different organizations don't assign the same URI to different resources (sort of like giving the same Social Security number or driver's license number or bank account number to two different people).<br /><br />This sort of material may sound obvious when we read it. But it is frequently <span style="font-weight:bold;">not</span> obvious to everyone involved in building, deploying, managing, and evolving software systems. In fact, I think failure to make these sorts of issues clear at the architectural and administrative levels is quite possibly the single greatest cause of problems in managing software systems in the real world.http://viewfromthefringe.blogspot.com/2010/03/parsing-architecture-of-world-wide-web.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-6221476707491157300Wed, 03 Mar 2010 05:15:00 +00002010-03-02T23:15:42.496-06:00architecture of the webURIwwwPrincipals of the WebAs I noted in a <a href="http://viewfromthefringe.blogspot.com/2010/03/architecture-of-world-wide-web-volume-1.html">previous entry</a>, I've been reading <a href="http://www.w3.org/TR/webarch/">Architecture of the World Wide Web, Volume One</a> and am finding it a great read. For example, take this little gem:<br /><blockquote><i>Constraint: URIs Identify a Single Resource</i><br /><br /> Assign distinct URIs to distinct resources.</blockquote><br />In a nutshell the authors have made it clear that a URI should refer to a particular resource. And just a bit further on they point out that URI's can be aliases for a single resource:<br /><blockquote>Just as one might wish to refer to a person by different names (by full name, first name only, sports nickname, romantic nickname, and so forth), Web architecture allows the association of more than one URI with a resource. URIs that identify the same resource are called <strong>URI aliases</strong>. The section on URI aliases (§2.3.1) discusses some of the potential costs of creating multiple URIs for the same resource.</blockquote><br />They even offer thoughts on the performance consequences of aliases.<br /><br />Ya gotta love it... :-)http://viewfromthefringe.blogspot.com/2010/03/principals-of-web.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-1404552199099748756Mon, 01 Mar 2010 06:18:00 +00002010-03-01T00:25:10.147-06:00architecturegood documentationword wide webwwwThe Architecture of the World Wide Web - Volume 1As part of my foray into RESTful services, I've been reading <a href="http://www.w3.org/TR/webarch/">The Architecture of the World Wide Web - Volume 1</a> and find it refreshingly informative. For example:<br /><br /><blockquote>The choice of syntax for global identifiers is somewhat arbitrary; it is their global scope that is important. The Uniform Resource Identifier, [URI], has been successfully deployed since the creation of the Web. There are substantial benefits to participating in the existing network of URIs, including linking, bookmarking, caching, and indexing by search engines, and there are substantial costs to creating a new identification system that has the same properties as URIs.</blockquote><br /><br />And:<br /><br /><blockquote>A resource should have an associated URI if another party might reasonably want to create a hypertext link to it, make or refute assertions about it, retrieve or cache a representation of it, include all or part of it by reference into another representation, annotate it, or perform other operations on it. Software developers should expect that sharing URIs across applications will be useful, even if that utility is not initially evident.</blockquote><br /><br />There is so much packed into each of these brief statements, and they are in equivalent of the first 10 pages of the document.<br /><br />I find it both amazing and sad that this document was published in 2004 yet I've found very few references to it in the six years since it's publication. Maybe I just haven't been looking in the right places?<br /><br />I will share additional passages that I find enlightening in the days (and weeks?) to come.http://viewfromthefringe.blogspot.com/2010/03/architecture-of-world-wide-web-volume-1.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-1445715675742485400Sat, 27 Feb 2010 21:30:00 +00002010-02-27T19:18:16.864-06:00documentationGWTopen sourcephilanthropySmartGWTsupportGWT and SmartGWTI'm doing some work with <a href="http://code.google.com/webtoolkit/">GWT 2.0</a> and also with <a href="http://code.google.com/p/smartgwt/">SmartGWT</a>. I like both toolkits, but I'm in the midst of a very steep learning curve (meaning I'm learning a lot quickly). While I'm making good progress in becoming proficient, I'm finding that SmartGWT suffers from the same problem that many open source products with commercial support options suffer from: weak documentation.<br /><br />It's completely understandable and I don't blame the developer(s) of SmartGWT. When you are working on an open source project and also trying to provide commercial support as a means of revenue, it's hard to find the time to produce good documentation. And the truth is that if you provide a good product with documentation good enough that developers don't need your services, you have just put yourself out of business.<br /><br />Such is the nature of open source projects that have commercial support models as their primary financing model. There's nothing that can be done about it (except find a philanthropist who will fund the project; and philanthropists of any sort are a rare breed these days, not to mention those interested in the the obscurity of open source software).<br /><br />So I'll keep climbing the learning curve with the documentation as is, and take good notes for the future when I've stopped working with these technologies and come back to them.<br /><br />Oh, and if you know any philanthropists looking to fund open source projects, I've got a project or two of my own I can suggest...http://viewfromthefringe.blogspot.com/2010/02/gwt-and-smartgwt.htmlnoreply@blogger.com (Brian Gilstrap)8tag:blogger.com,1999:blog-8355257.post-9204629069112070924Wed, 10 Feb 2010 23:38:00 +00002010-02-10T17:43:30.103-06:00dependency injectionGuiceJavaGuice 2.0 - tastyI'm finally getting a chance to do some work with <a href="http://code.google.com/p/google-guice/">Guice 2.0</a>. I don't know if I just couldn't wrap my head around Guice in the past and I've finally "gotten" it, or if Guice 2.0 provides a more approachable API. Either way, I'm finding it great to use.<br /><br />I've been mildly whiny about Guice in the past, stating that it wasn't completely statically typed (which is true, since it's possible you'll ask for a resource at runtime that isn't available because it wasn't configured). But even with that small limitation, I'm finding Guice 2.0 to be far better than Spring for dependency injection. The code is much smaller, much more type-safe, not XML (a big plus), and much more strongly type-checked.<br /><br />If you haven't checked out Guice, or if you tried Guice 1.0 but haven't tried out 2.0, you should give 2.0 a serious look.<br /><br />Now I need to integrate Guice with <a href="https://jersey.dev.java.net/">Jersey</a> (the JAX-RS reference implementation)...http://viewfromthefringe.blogspot.com/2010/02/guice-20-tasty.htmlnoreply@blogger.com (Brian Gilstrap)3tag:blogger.com,1999:blog-8355257.post-2596587269855112108Mon, 08 Feb 2010 20:21:00 +00002010-02-08T14:41:05.045-06:00anti-patternsJavaJAX-WSSOASOAPWS-*WSDLJAX-WS tarpitI'm currently doing some work with an open source framework (to be left unnamed) built upon JAX-WS (using SOAP, SAML, and HTTPS). I'm making headway working with it, but talk about a tar pit. You go into the code and it's almost impossible to get out. Every time you gain a bit of understanding, some other anti-pattern slaps you until you see starts and you're back to global searches with google and looking through the code to make further progress.<br /><br />Violations of the <a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> principle are rampant. The generated code has magic constants sprinkled liberally throughout. Generated classes have default constructors and public getters and setters for all fields despite the fact that some fields are actually required; no hashCode; no equals; no ability to determine if one of these data objects is valid or not. Turning the WSDL into code generates vast numbers of classes (reminding me of the terrible mapping from CORBA IDL to Java). The generated code has almost no helpful comments (despite the fact that generating at least some reasonable back-references in generated code is easy precisely because you are generating code). And we've only delved a bit into the SAML aspects of things; I expect that to be another can of worms entirely.<br /><br />I've heard that the SOAP/WS-* specifications were co-opted by certain large companies and made so complex it's nearly impossible to work with them except with the very big IDEs-with-god-complexes those vendors sell. Based on these experiences and previous ones working with some SOA frameworks, I can believe it. If that isn't the reason for their incredible opaqueness and anti-patterns, I'm afraid to find out who thought all of this was a good idea.<br /><br />So while I'm making progress and getting things done, the entire experience makes me want to go wash my hands and update my resume.<br /><br />If you know of good ways to work with this stuff short of plunking down vast quantities of money for some IDE that will sort-of/mostly/kind-of hide all the complexity (until the moment things break and you really need to understand what is going on), I'd love to hear about them...http://viewfromthefringe.blogspot.com/2010/02/jax-ws-tarpit.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-8189024736795919422Tue, 02 Feb 2010 17:35:00 +00002010-02-02T11:35:43.073-06:00annotationsdangerframeworksJavaJava Annotations have Become Pixie DustI was giving a talk about RESTful services using JAX-RS and Jersey recently and was asked why I had used Mark Volkmann's <a href="http://java.ociweb.com/mark/programming/wax.html">WAX</a> for generating HTML and XML. The person asking the question pointed out that Jersey has integration with JAXB.<br /><br />There were two answers to that question. One answer is that I am leery of anything which automatically converts my Java objects into a serialized format (bitten by Java's object serialization in the past). Incompatible object changes can be difficult or impossible to reconcile in a backward-compatible manner.<br /><br />But the main answer I gave got some chuckles and further questions. I explained I was trying to avoid too much "pixie dust". In the example code, I was already using the Java Persistence API (JPA) and JAX-RS and their associated annotations. If I had not been careful, there would have been annotations for Spring and JAXB as well. All of these annotations are small in the code but have very large effects. Those innocent looking annotations subject my poor unsuspecting code to some very big (and some would argue bad) frameworks. Understanding how these frameworks interact is not only hard, but those interactions change as the frameworks change (possibly resulting in the system breaking with no code changes).<br /><br />I have real misgivings about the number of annotation-based technologies that should be applied to any one project. Each annotation you use represents some amount of code you don't have to write. And that is, of course, a good thing from a development perspective. But every annotation you use represents 'pixie dust', behavior which is hidden from you and generally performed at runtime. That means that when things go wrong, there isn't any code to look at. It also means that small differences between configurations can produce large changes in behavior. That's a very bad thing for testing, production deployment, production maintenance, etc.<br /><br />I've been thinking about this issue for some time*, so I was pleasantly surprised to find Stephen Schmidt's post admonishing us to <a href="http://codemonkeyism.com/beware-magical-code">Be careful with magical code</a>. His post is not specific to annotations (he calls out aspect-oriented programming, for example - I agree that AOP is another kind of pixie dust). And he points out some examples of the "pixie dust" phenomenon. While I don't agree with his 'magic containment' example, it's a good post. You should read it.<br /><br />As a rule of thumb, I think two kinds of pixie dust is the maximum to sprinkle on a project. So think hard and choose wisely when picking which ones to use: the more kinds of pixie dust you sprinkle, the harder it will be for you and others to understand and troubleshoot things, now and especially in the future.<br /><br /><br /><br /><i>*Thanks to <a href="http://codetojoy.blogspot.com/">Mike Easter</a> for planting the idea of talking about the state of annotations in Java</i>http://viewfromthefringe.blogspot.com/2010/02/java-annotations-have-become-pixie-dust.htmlnoreply@blogger.com (Brian Gilstrap)4tag:blogger.com,1999:blog-8355257.post-2600550687420546533Fri, 08 Jan 2010 23:15:00 +00002010-01-20T19:36:23.243-06:00Taxonomy of Technical Blog PostsI categorize technical blog postings into a taxonomy:<br/><br /><div>Type I: Describing how to use some kind of technology, your own or someone else's</div><div>Type II: Describing how to overcome some limitation, bug, or quirk of technology</div><div>Type III: Whining about failures to get one or more technologies to work (together)</div><div>Type IV: Crowing about getting one or more technologies to work (together) - often a follow-up to a Type III posting</div><div>Type V: Indulging in a post that really doesn't belong in a technical blog.</div><div><br /></div><div>For a classic example of a Type II posting, see:</div><div><br /></div><div><a href="http://codetojoy.blogspot.com/2010/01/grails-tip-for-internet-connections.html">http://codetojoy.blogspot.com/2010/01/grails-tip-for-internet-connections.htm</a>l</div>http://viewfromthefringe.blogspot.com/2010/01/i-categorize-technical-blog-postings.htmlnoreply@blogger.com (Brian Gilstrap)3tag:blogger.com,1999:blog-8355257.post-8630694098824095787Wed, 06 Jan 2010 04:24:00 +00002010-01-05T23:07:58.828-06:00AjaxexampleJAX-RSJerseyJSONRESTA RESTful web service testbedIt's time to build a more complete RESTful service example. In this web service, we'll aim to perform all the common activities of a real service. This is often referred to by the acronym CRUD, which stands for <a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">Create, Read, Update, and Delete</a> for all of the common operations associated with persistent data. Since we're dealing with a RESTful web service, we'll also throw in multiple representations for a resource, and connectedness to make it easy for clients to navigate through the service resources.<div><br /></div><div>So, what should our service do? My friend and colleague, <a href="http://java.ociweb.com/mark/">Mark Volkmann</a>, introduced the eample of a database of music information. In this example, the service service contains information about several kinds of resources:</div><div><ul><li>Music artists (Artists)</li><li>Albums</li><li>Songs</li></ul>This is a nice example because it is rich enough to expose many of the problems that need to be solved by RESTful web service without becoming so large that it's unwieldy to explore. The domain model is immediately familiar, so we can focus on the technologies and not the model.</div><div><br /></div><div>With this service we can explore not only traditional browser-based HTML, but XML and even an Ajax client using JSON. We can also use it as testbed for things like caching, security, clustering/failover, and composition of services.</div>http://viewfromthefringe.blogspot.com/2009/07/restful-web-service-testbed.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-4680822815826310513Tue, 08 Dec 2009 05:10:00 +00002009-12-07T23:13:52.784-06:00JAX-RSJerseyJSR-311JUGSt. Louis Java User's GroupJAX-RS and Jersey talk materialsI recently gave a <a href="http://java.ociweb.com/javasig/knowledgebase/2009-11/index.html">talk</a> on JAX-RS, JSR 311, and Jersey at the St. Louis Java User's Group website. You can find the <a href="http://java.ociweb.com/javasig/knowledgebase/2009-11/JAX-RS%20and%20Jersey.pdf">presentation</a> and the <a href="http://java.ociweb.com/javasig/knowledgebase/2009-11/JAX-RS-JUG-Nov-09.jar">sample code</a> on their <a href="http://java.ociweb.com/javasig/">website</a>.http://viewfromthefringe.blogspot.com/2009/12/jax-rs-and-jersey-talk-materials.htmlnoreply@blogger.com (Brian Gilstrap)0tag:blogger.com,1999:blog-8355257.post-2458662373784370837Fri, 13 Nov 2009 16:25:00 +00002009-11-13T11:11:44.019-06:00303bugcurlGEThttp redirectMac OS XPOSTCurl has a bug with redirects?<div style="font-size:medium;">I gave a presentation at the JUG last night about JAX-RS and Jersey. The talk went well, and it was a great crowd with a lot of participation (I'll post more once I have the materials cleaned up and they are posted to the JUG website).<br /><br />But one thing that didn't go right last night was when I was using the 'curl' command line tool to create new artists in the RESTful music database service I was using as an example.<br /><br />The curl man page on my MacBook Pro says:</div><br /><div style="font-family:'Times New Roman';font-size: medium;"><i>When curl follows a redirect and the request is not a plain GET (for example POST or PUT), it will do the following request with a GET if the HTTP response was 301, 302, or 303. If the response code was any other 3xx code, curl will re-send the following request using the same unmodified method.</i><br /><br />Yet, when I was using it, it wasn't doing that. For example when trying to create artist 'Jane':<br /></div><br /><div style="font-size:small; font-family:'courier new';"><br />brian@Widget: curl -v -H "Accept: text/html" -X POST -L "http://localhost:3131/artists/Jane"<br />* About to connect() to localhost port 3131 (#0)<br />* Trying ::1... Connection refused<br />* Trying fe80::1... Connection refused<br />* Trying 127.0.0.1... connected<br />* Connected to localhost (127.0.0.1) port 3131 (#0)<br />> POST /artists/Jane HTTP/1.1<br />> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3<br />> Host: localhost:3131<br />> Accept: text/html<br />> <br />< HTTP/1.1 303 See Other<br />< server: grizzly/1.9.10<br />< Location: http://localhost:3131/artists/id/313<br />< Content-Type: text/plain; charset=iso-8859-1<br />< Content-Length: 0<br />< Date: Fri, 13 Nov 2009 05:24:12 GMT<br />< <br />* Connection #0 to host localhost left intact<br />* Issue another request to this URL: 'http://localhost:3131/artists/id/313'<br />* Re-using existing connection! (#0) with host localhost<br />* Connected to localhost (127.0.0.1) port 3131 (#0)<br />> POST /artists/id/313 HTTP/1.1<br />> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3<br />> Host: localhost:3131<br />> Accept: text/html<br />> <br />< HTTP/1.1 405 Method Not Allowed<br />< server: grizzly/1.9.10<br />< Allow: DELETE,OPTIONS,HEAD,GET<br />< Content-Type: text/plain; charset=iso-8859-1<br />< Content-Length: 0<br />< Date: Fri, 13 Nov 2009 05:24:12 GMT<br />< <br /></div><br /><div style="font-size:medium;">When it followed the redirect and issued the follow-up request, it went to the right place:</div><br /><div style="font-size:small; font-family:'courier new';"><br />...<br />< HTTP/1.1 <strong>303 See Other</strong><br />< server: grizzly/1.9.10<br />< Location: <strong><span style="color= #5555CC;">http://localhost:3131/artists/id/313</span></strong><br />...<br />* Connection #0 to host localhost left intact<br />* Issue another request to this URL: '<strong><span style="color = #6600CC;">http://localhost:3131/artists/id/313</span></strong>'<br /></div><br /><div style="font-size:medium;">But it didn't switch to GET:<br /></div><div style="font-size:small; font-family:'courier new';"><br />> <strong><span style="color:#FF0000;">POST</span></strong> /artists/id/313 HTTP/1.1<br /></div><br /><div style="font-size:medium;">As a result, I got a 405 error code because curl tried to do a POST to the redirected URL rather than doing a GET as is documented.<br /><br />I did some further experiments last evening after the talk and confirmed this behavior. I even tried switching the return code to a 302 just to see if it would make a difference and it doesn't.<br /><br />How does your curl behave? Does it switch from POST to GET when following a redirect using the -L option?<br /><br />I'd appreciate it if anyone who knows anything about this could comment. Is curl doing the wrong thing, or is the man page wrong and curl is no longer supposed to do this?<br /></div>http://viewfromthefringe.blogspot.com/2009/11/curl-has-bug-with-redirects.htmlnoreply@blogger.com (Brian Gilstrap)4tag:blogger.com,1999:blog-8355257.post-1733437357564833496Wed, 22 Jul 2009 03:00:00 +00002009-07-21T23:53:40.735-05:00clientJavaJAX-RSJerseyRESTRESTfulweb servicesRESTful clients with the Jersey Client API<div><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">[Note: This is one in a </span></span><a href="http://viewfromthefringe.blogspot.com/2009/06/jax-rs-and-jersey-posts.html"><span style="color:#55178d;"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">series of posts</span></span></span></a><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;"> about JAX-RS. You may wish to start at </span></span><a href="http://viewfromthefringe.blogspot.com/2009/05/exploring-restful-web-services-with-jax.html"><span style="color:#55178d;"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">the beginning</span></span></span></a><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">]</span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia; min-height: 16.0px"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;"><br /></span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="color:#55198c;"><a href="https://jersey.dev.java.net/"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">Jersey</span></span></a></span><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">, the reference implementation of the JAX-RS specification, is a framework that makes it very easy to implement RESTful web services. But there is more to Jersey than just the server side. In this post I will spend a little time exploring the Jersey client library.</span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia; min-height: 16.0px"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;"><br /></span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">Depending upon how you design your RESTful service, you may or may not want to have separate URLs for separate representations of the same resource. This presents a problem when trying to test with a browser. There is no way to tell popular browsers that you want a text/html representation or an image/jpeg representation. The browser has a list of preferred media types, but none that I'm aware of allow you to customize this (either in general or for a particular request). Even more importantly, we need to be able to build solid unit tests for our services. The Jersey client framework provides a good solution to this problem. It is designed to allow developers of RESTful web services to write good unit tests, but is more general purpose than that. It can also be used to write RESTful client applications.</span></span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Times New Roman; min-height: 16.0px"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;"><br /></span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">There is an </span></span><a href="https://www.sun.com/offers/details/Java_Jersey_Client.xml"><span style="letter-spacing: 0.0px ;color:#00009d;"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">excellent tutorial</span></span></span></a><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;"> on the Jersey client API, which you should download and read if you plan to use it. But I will give you a taste of the API here.</span></span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia; min-height: 16.0px"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;"><br /></span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">The Jersey client API is very clean, and requires a minimum of fuss to use. As an example, let's write a unit test which tests our web service serving up information about fighter planes. First, we write the code to set up and tear down our service implementation. This code is the same as that in our Main class before, just split up between the setup and tear-down methods.</span></span></p><pre>public class Test3b {<br /><br />private SelectorThread selector;<br /><br />@org.junit.Before<br />public void createService() throws IOException {<br /> Map<string, string=""> initParams = new HashMap<string, string="">();<br /> initParams.put("com.sun.jersey.config.property.packages", "net.gilstraps.server");<br /> selector = GrizzlyWebContainerFactory.create("http://localhost:9998/", initParams);<br />}<br /><br />@org.junit.After<br />public void DestroyService() {<br /> selector.stopEndpoint();<br /> selector = null;<br />}<br /></string,></string,></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">The only difference in this case is we don't read from standard in to shut down the service, since we always want to shut it down at the end of the unit test.</span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Times New Roman; min-height: 16.0px"><span class="Apple-style-span" style="font-size:medium;"><br /></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">Now, we can test that the text/html representation returned by invoking the service is what we expect. First, we do a bit of hoop jumping to read in a copy of the HTML we expect to receive:</span></span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><br /></p><pre>@org.junit.Test<br />public void testF22Html() {<br />try {<br /> File expected = new File("f22.html");<br /> long fileSize = expected.length();<br /> if (fileSize > Integer.MAX_VALUE) {<br /> throw new IllegalArgumentException("File it larger than a StringWriter can hold");<br /> }<br /> int size = (int) fileSize;<br /> BufferedReader r = new BufferedReader(new FileReader(expected), size);<br /> char[] chars = new char[size];<br /> int readChars = r.read(chars);<br /> if (readChars != size) {<br /> throw new RuntimeException("Failed to read all chars of the expected result html file");<br /> }<br /> final String expectedText = new String(chars);<br /></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span class="Apple-style-span" style="font-size:medium;">At this point, the variable expectedText contains what we should receive back from our request. Making the request is quite straightforward. First, we create a JAX-RS client:</span></p><pre>Client client = new Client();<br /></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">These clients are 'heavyweight' objects (taking time to create and using significant resources). As such, in a production client we would want to create this client once and use it many times. For the sake of independent unit tests, we will go ahead and create a Client object for each test.</span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Times New Roman; min-height: 16.0px"><span class="Apple-style-span" style="font-size:medium;"><br /></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">Next, we specify the resource we want to retrieve using a WebResource object:</span></span></p><pre>WebResource f22 = client.resource("http://localhost:9998/planes/f22/f22.html");<br /></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">And then we ask the client to retrieve the resource for us, specifying that we want a text/html representation (MediaType.TEXT_HTML_TYPE) and specifying that we want to get back a ClientResponse object:</span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia; min-height: 16.0px"><br /></p><pre>ClientResponse response =<br />f22.accept(MediaType.TEXT_HTML_TYPE).get(ClientResponse.class);<br /></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">This code is using the builder pattern, where we build up our request through a chain of method calls. In this case, the chain is only two calls long. First, we call the accept method to specify the media types we will accept in the response (</span><i><span class="Apple-style-span" style="font-size:medium;">text/html</span></i><span class="Apple-style-span" style="font-size:medium;">), then we call the get method to actually retrieve the resource. It is possible to chain together more calls to specify other characteristics of either the request or the expected response (for more information, see the whitepaper on the Jersey client API).</span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Times New Roman; min-height: 16.0px"><span class="Apple-style-span" style="font-size:medium;"><br /></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">Now that we have gotten a representation of the resource in the form of an HTTP response, we can then retrieve the HTML entity contained within that response as a string:</span></span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Times New Roman; min-height: 16.0px"><br /></p><pre>String returnedHTML = response.getEntity(String.class);<br /><pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">And finally, since this is a unit test, we make sure that what we got back matches what we expected:</span></span></p><pre>assertEquals("Expected and actual HTML don't match"<br />expectedText, returnedHTML);<br /></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span class="Apple-style-span" style="white-space: pre-wrap; "><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">The ClientResponse object is useful if you want to look at other characteristics of the response, such as the returned headers. In this case, we could just as well have asked for the string from the WebResource directly. To do so, we would replace these two lines:</span></span></span></p></pre><p></p><pre>ClientResponse response =<br />f22.accept(MediaType.TEXT_HTML_TYPE).get(ClientResponse.class);<br />String returnedHTML = response.getEntity(String.class);<br /></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Georgia"><span style="letter-spacing: 0.0px"><span class="Apple-style-span" style="font-size:medium;">With this one:</span><span class="Apple-style-span" style="font-family:monospace, fantasy;"><span class="Apple-style-span" style="font-size:medium;"> </span></span></span></p><pre>String returnedHTML = asHTML.accept(MediaType.TEXT_HTML_TYPE).get(String.class);<br /></pre><p style="margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Georgia"></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Georgia; color:#333333;"><span class="Apple-style-span" style="font-family:monospace, -webkit-fantasy;font-size:100%;color:#000000;"><span class="Apple-style-span" style="font-size:13px;"><span class="Apple-style-span" style="font-family:Georgia, -webkit-fantasy;font-size:130%;"><span class="Apple-style-span" style="font-size:14px;"><span class="Apple-style-span" style=" white-space: normal; font-family:Georgia, fantasy;font-size:16px;"></span></span></span></span></span></p><span class="Apple-style-span" style="font-family:monospace, -webkit-fantasy;font-size:100%;color:#000000;"><span class="Apple-style-span" style="font-family:Georgia, -webkit-fantasy;font-size:130%;"><pre><pre><p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font: normal normal normal 14px/normal Georgia; "><span class="Apple-style-span" style="white-space: pre-wrap; "><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:medium;">Testing for retrieval of the JPEG representation of an image is almost identical. The only differences are that we read in the image file as an array of bytes, ask for the response entity as an array of bytes, and compare the two as arrays of bytes. Here is the entire test method:</span></span></span></p></pre><p></p></pre></span></span><p></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Georgia; color:#333333;"><span class="Apple-style-span" style="color: rgb(0, 0, 0); font-family:monospace, fantasy;font-size:13px;"><span class="Apple-style-span" style=" ;font-family:Georgia, fantasy;font-size:14px;"></span></span></p><pre><br />@org.junit.Test<br />public void testF22JPEG() {<br /> try {<br /> // Read in our expected result<br /> File imageFile = new File("f22.jpg");<br /> long fileSize = imageFile.length();<br /> if (fileSize > Integer.MAX_VALUE) {<br /> throw new IllegalArgumentException("File it larger than a StringWriter can hold");<br /> }<br /> int size = (int) fileSize;<br /> byte[] expectedBytes = new byte[size];<br /> BufferedInputStream bis = new BufferedInputStream(new FileInputStream(imageFile), size);<br /> int bytesRead = bis.read(expectedBytes);<br /> assertEquals(size, bytesRead);<br /><br /> // Request the representation<br /> Client client = new Client();<br /> WebResource f22 = client.resource("http://localhost:9998/planes/f22/f22.jpg");<br /> ClientResponse response = f22.accept(MediaType.WILDCARD).get(ClientResponse.class);<br /> MultivaluedMap<string,string> headers = response.getHeaders();<br /> for ( String key : headers.keySet() ) {<br /> System.out.println( key + "=" + headers.get(key) );<br /> }<br /> byte[] returnedBytes = new byte[0];<br /> returnedBytes = response.getEntity(returnedBytes.getClass());<br /><br /> // Compare the two sets of bytes to make sure they match<br /> assertEquals(size, returnedBytes.length);<br /> assertTrue(Arrays.equals(expectedBytes,returnedBytes));<br /> }<br /> catch (FileNotFoundException e) {<br /> AssertionError ae = new AssertionError("File containing expected HTML not found!");<br /> ae.initCause(e);<br /> throw ae;<br /> }<br /> catch (IOException e) {<br /> AssertionError ae = new AssertionError("Problems reading expected text!");<br /> ae.initCause(e);<br /> throw ae;<br /> }<br />}</string,string></pre><p style="margin: 0.0px 0.0px 16.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><span class="Apple-style-span" style="white-space: normal; "></span></p><pre><pre><div><span class="Apple-style-span" style="font-family:georgia, fantasy;"><span class="Apple-style-span" style="white-space: pre-wrap; "><span class="Apple-style-span" style="font-size:medium;">Just like the core of Jersey, the client library enables clear, short code that clearly expresses what the developer is trying to do. This is a marked departure from many other web frameworks and APIs. It is refreshing to be able to write web services clients in such a terse, yet clear style.</span></span></span></div><div><span class="Apple-style-span" style="font-family:georgia, -webkit-fantasy;font-size:130%;"><span class="Apple-style-span" style="font-size: 16px; white-space: pre-wrap;"><br /></span></span></div><div><span class="Apple-style-span" style="font-family:georgia, -webkit-fantasy;font-size:130%;"><span class="Apple-style-span" style="font-size: 16px; white-space: pre-wrap;">[Note: I plan to continue working with Jersey and continue posting about it. But I'm going to take a few weeks to look into some other topics first. I'll make sure to update the <a href="http://viewfromthefringe.blogspot.com/2009/06/jax-rs-and-jersey-posts.html">list of JAX-RS and Jersey posts</a> as I add more]</span></span></div></pre><p></p></pre><p></p><p></p></pre></div><div><pre><pre><p></p></pre></pre></div>http://viewfromthefringe.blogspot.com/2009/07/restful-clients-with-jersey-client-api.htmlnoreply@blogger.com (Brian Gilstrap)6