<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-9159309</id><updated>2012-01-20T21:40:30.643-05:00</updated><category term='jta'/><category term='glassfish'/><category term='jquery'/><category term='primefaces'/><category term='jsf'/><category term='jms'/><category term='j2ee'/><category term='ejb'/><category term='java'/><category term='jpa'/><category term='ajax'/><category term='mdb'/><title type='text'>Bill Schneider, Software Architect</title><subtitle type='html'>&lt;a href="http://www.linkedin.com/in/wrschneider"&gt;Bill Schneider&lt;/a&gt; writes about software and other stuff</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>30</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-9159309.post-3480892172530070623</id><published>2012-01-11T21:22:00.000-05:00</published><updated>2012-01-15T21:28:40.024-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='ejb'/><category scheme='http://www.blogger.com/atom/ns#' term='mdb'/><category scheme='http://www.blogger.com/atom/ns#' term='jta'/><category scheme='http://www.blogger.com/atom/ns#' term='jms'/><category scheme='http://www.blogger.com/atom/ns#' term='j2ee'/><category scheme='http://www.blogger.com/atom/ns#' term='glassfish'/><title type='text'>Handling poison JMS messages in Glassfish - infinite loop WTF?</title><content type='html'>I found this post useful for understanding problems with handling "poison messages" in message-driven beans:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://weblogs.java.net/blog/felipegaucho/archive/2009/09/24/handling-poison-messages-glassfish"&gt;http://weblogs.java.net/blog/felipegaucho/archive/2009/09/24/handling-poison-messages-glassfish&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The gist of it is that even if you think you caught an exception, your transaction still might roll back and cause the JMS message to be re-delivered. &amp;nbsp;(The MDB is an EJB, which will default to container-managed transactions, equivalent to having REQUIRED on each method.)&lt;br /&gt;&lt;br /&gt;There are two small caveats to add:&lt;br /&gt;&lt;br /&gt;1. The specific issue with JPA is that certain persistence exceptions mark the transaction for rollback, even if the exception is actually caught. &amp;nbsp;It matters not whether that JPA activity is happening directly in onMessage() or in a "sub-transaction".&lt;br /&gt;&lt;br /&gt;2. Simply annotating another method in the same MDB with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) does &lt;i&gt;not &lt;/i&gt;actually create a separate transaction context. &amp;nbsp;If you are calling a method locally within the same EJB, "this" refers to the object itself, while the transaction behavior lives in the EJB proxy wrapper. &amp;nbsp;(See&amp;nbsp;&lt;a href="http://stackoverflow.com/questions/427452/ejb-transactions-in-local-method-calls"&gt;http://stackoverflow.com/questions/427452/ejb-transactions-in-local-method-calls&lt;/a&gt;) &amp;nbsp;So you actually have to put this transaction in a separate EJB and inject it into the MDB with @Inject or @EJB annotations.&lt;br /&gt;&lt;br /&gt;And now for the "WTF" part.&lt;br /&gt;&lt;br /&gt;The thing that really surprised me is the retry behavior. &amp;nbsp;If the MDB throws an exception, and the transaction rolls back as a result, Glassfish recognizes that there was an exception and will only re-deliver the message once. &amp;nbsp;There is some setting somewhere that controls how many retries are attempted, I believe. &amp;nbsp;(Haven't found it.)&lt;br /&gt;&lt;br /&gt;If you catch an exception that marked the transaction for rollback (or the transaction was marked for rollback programmatically), the transaction still rolls back, and the message is redelivered. &amp;nbsp;However, the rollback without exception does &lt;i&gt;not &lt;/i&gt;fire Glassfish's retry counter, so you end up in an infinite loop.&lt;br /&gt;&lt;br /&gt;Either way the solution is the same, but still -WTF!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-3480892172530070623?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/3480892172530070623/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=3480892172530070623' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3480892172530070623'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3480892172530070623'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2012/01/handling-poison-jms-messages-in.html' title='Handling poison JMS messages in Glassfish - infinite loop WTF?'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-7767464144763430126</id><published>2012-01-07T21:23:00.001-05:00</published><updated>2012-01-13T21:52:39.924-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='jpa'/><title type='text'>equals and hashCode on @Entity classes: just say no</title><content type='html'>I've come to the conclusion that you should avoid defining equals and hashCode on JPA @Entity objects unless you have a really good reason to.&lt;br /&gt;&lt;br /&gt;Doing it right is non-trivial, with all the gotchas and caveats.&amp;nbsp; First, the logistics:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;You can't use the primary key because multiple unsaved objects all have a null PK and would be "equal".&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Using the PK with object identity as fallback means an object won't be equal to itself before and after being persisted, and hash-based collections won't work properly if the hash code changes during the collection's lifespan.&lt;/li&gt;&lt;li&gt;A unique business key could work, if that's an option - but you have to remember to maintain the equals and hashCode method if those properties or relations change. &amp;nbsp;Also if that key isn't immutable, you have a similar problem as with PK + fallback above.&lt;/li&gt;&lt;li&gt;Creating a UUID in the constructor just to make equals and hashcode work is... yucky.&lt;/li&gt;&lt;li&gt;Often these methods are insufficiently covered by unit tests.&lt;/li&gt;&lt;/ul&gt;Second: there's the deeper question of what should "equals" mean in the context of a mutable business entity? &amp;nbsp;What comparison makes sense is often context-dependent. &amp;nbsp;Sometimes you might want to compare based on primary key, other times you might want to compare on some other property.&lt;br /&gt;&lt;br /&gt;So what's the worst thing that happens if you just don't do anything, and take the default based on object identity? &lt;br /&gt;&lt;br /&gt;Usually, you won't even miss these methods. &amp;nbsp;The default implementation will work fine unless you're trying to compare objects loaded in two&amp;nbsp;&lt;i&gt;different&lt;/i&gt;&amp;nbsp;persistence sessions/transactions,with either equals() or contains(). &amp;nbsp;&amp;nbsp;I've found this is the exception case - much of my collection manipulation in practice is manipulating objects all loaded in the same session to hand off to the view layer, so HashSet recognizes duplicates and contains() works just fine. &amp;nbsp;And I almost never use an entity as a key in a hashmap.&lt;br /&gt;&lt;br /&gt;The price you pay is more verbosity when you do need to compare objects between a session-scoped cache and the current request. &amp;nbsp;You have to remember that obj1.equals(obj2) doesn't work, and needs to be replaced with obj1.getId().equals(obj2.getId()). &amp;nbsp;Contains is a bit more verbose, and a utility method like containsById(collection, obj) might be helpful. &amp;nbsp;Some people will say it's confusing that equals doesn't "just work" but I find it less confusing to be explicit about what you're comparing on - and less confusing than a broken or unmaintained equals() method.&lt;br /&gt;&lt;br /&gt;Finally, if this were C#, we wouldn't even have this discussion. &amp;nbsp;With closures and LINQ extension methods we would just say "collection.Where(obj =&amp;gt; obj.id = foo.id)" and be done with it!&lt;br /&gt;&lt;br /&gt;Related links:&lt;br /&gt;http://community.jboss.org/wiki/EqualsAndHashCode&lt;br /&gt;&lt;br /&gt;http://onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html?page=3&lt;br /&gt;&lt;br /&gt;http://stackoverflow.com/questions/1929445/to-equals-and-hashcode-or-not-on-entity-classes-that-is-the-question&lt;br /&gt;&lt;br /&gt;http://stackoverflow.com/questions/1638723/equals-and-hashcode-in-hibernate&lt;br /&gt;&lt;br /&gt;&lt;a href="https://forum.hibernate.org/viewtopic.php?t=928172"&gt;https://forum.hibernate.org/viewtopic.php?t=928172&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.ibm.com/developerworks/java/library/j-jtp05273/index.html"&gt;http://www.ibm.com/developerworks/java/library/j-jtp05273/index.html&lt;/a&gt;: "For mutable objects, the answer is not always so clear. Should equals() and hashCode() be based on the object's identity (like the default implementation) or the object's state (like Integer and String)? There's no easy answer -- it depends on the intended use of the class...&amp;nbsp;It is not common practice to use a mutable object like a List as a key in a HashMap."&lt;br /&gt;&lt;div&gt;&lt;br /&gt;http://burtbeckwith.com/blog/?p=53&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-7767464144763430126?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/7767464144763430126/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=7767464144763430126' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7767464144763430126'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7767464144763430126'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2012/01/equals-and-hashcode-on-entity-classes.html' title='equals and hashCode on @Entity classes: just say no'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-2783649607286827668</id><published>2012-01-03T15:53:00.000-05:00</published><updated>2012-01-07T21:26:47.386-05:00</updated><title type='text'>Code coverage - unintended benefit</title><content type='html'>Unit test coverage is not a panacea. &amp;nbsp;You can reach 100% unit test coverage without a single assertion about outputs or business logic, in which case the tests aren't that useful.&lt;br /&gt;&lt;br /&gt;Or are they? &amp;nbsp;Just the act of executing every single line and branch of code does provide one important value: it demonstrates that you can show what inputs or conditions trigger which parts of the code. &lt;br /&gt;&lt;br /&gt;More importantly, the act of getting there helps reveal sections of code and conditionals that you might not even need. &amp;nbsp;Having a coverage target also gives you incentives to stay "clean" and avoid gratuitous not-null checks or try/catch blocks, two pet peeves of mine. &amp;nbsp;(I've seen too many not-null checks that looked defensive at first but in reality just kicked the can down the road, further obscuring the real problem.) &lt;br /&gt;&lt;br /&gt;The other benefit I've found is that a coverage goal encourages refactoring that you should be doing anyway, because well-factored code is easier to unit test. &amp;nbsp;For instance I had one JSF/JPA application where I was accessing JPA EntityManagers directly in the JSF managed beans. &amp;nbsp;This made unit tests on the managed beans annoying because I had to mock out EntityManager.createQuery and dozens of Query.setParameter calls for each JPA action. &amp;nbsp;By pulling the JPA actions into a separate DAO layer, I could just mock a single call to myDAO.getStuff(arguments). &amp;nbsp;Plus, after isolating the DAO, I could then write an integration test on the DAO hooking up to a real database.&lt;br /&gt;&lt;br /&gt;Other related links:&lt;br /&gt;&lt;a href="http://www.wakaleo.com/blog/316-code-coverage-as-a-refactoring-tool"&gt;http://www.wakaleo.com/blog/316-code-coverage-as-a-refactoring-tool&lt;/a&gt;&lt;br /&gt;&lt;a href="http://codebetter.com/patricksmacchia/2009/06/07/high-test-coverage-ratio-is-a-good-thing-anyway/"&gt;http://codebetter.com/patricksmacchia/2009/06/07/high-test-coverage-ratio-is-a-good-thing-anyway/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-2783649607286827668?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/2783649607286827668/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=2783649607286827668' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/2783649607286827668'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/2783649607286827668'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2012/01/unit-test-coverage-unintended-benefit.html' title='Code coverage - unintended benefit'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-5541309248805667849</id><published>2012-01-03T15:38:00.001-05:00</published><updated>2012-01-13T21:53:03.879-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='primefaces'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='jsf'/><title type='text'>Primefaces AJAX callbacks: onstart vs. onclick</title><content type='html'>I just learned the hard way that onstart and onclick are not the same thing.&lt;br /&gt;&lt;br /&gt;In particular, a "return ..." has very different semantics in both cases.&lt;br /&gt;&lt;br /&gt;Consider this code:&lt;br /&gt;&lt;pre style="brush: xml;"&gt;&amp;lt;p:commandLink action="#{bean.method}" onstart="return func()" ...&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If "func()" return false, this code will abort the AJAX request and bean.method() won't get called.&lt;br /&gt;If "func()" returns true, the AJAX request processes.&lt;br /&gt;If you replace onstart with onclick, the AJAX request will abort &lt;em&gt;even if func() returns true&lt;/em&gt;.&lt;br /&gt;&lt;br /&gt;That's because the Primefaces puts the code to generate the AJAX request in the onclick handler, pre-pending your code from the p:commandLink onclick before it. &amp;nbsp;If your code returns, the AJAX request never gets sent.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-5541309248805667849?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/5541309248805667849/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=5541309248805667849' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/5541309248805667849'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/5541309248805667849'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2012/01/primefaces-ajax-callbacks-onstart-vs.html' title='Primefaces AJAX callbacks: onstart vs. onclick'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-4773478234494441131</id><published>2011-12-30T22:32:00.000-05:00</published><updated>2011-12-30T22:32:52.417-05:00</updated><title type='text'>Atlas Debugged: The Fountainhead, YAGNI and "clean code"</title><content type='html'>The last time I re-read&amp;nbsp;&lt;i&gt;The Fountainhead, &lt;/i&gt;I felt like many of the ideas in the book could be useful for software developers. &amp;nbsp;It seemed like there are many parallels between the values demonstrated by the book's hero, Howard Roark, and good development practices. &lt;br /&gt;&lt;br /&gt;Many of the ideas dovetail nicely with some Agile development principles, especially the various "keep it simple" ones like&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/You_ain%27t_gonna_need_it"&gt;YAGNI&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt;&amp;nbsp;(along with DRY's cousins,"once and only once", and&amp;nbsp;&lt;a href="http://c2.com/cgi/wiki?ThreeStrikesAndYouRefactor"&gt;"three strikes and refactor"&lt;/a&gt;). &amp;nbsp;Roark's designs are driven entirely by purpose, function and constraints, with an open disdain for non-functional or ornamental additions. &amp;nbsp;For example, about the house he builds for Austen Heller, Roark says:&lt;br /&gt;&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;“Every piece of it is there because the house needs it – and for no other reason… You can see each&amp;nbsp;stress, each support that meets it... But you’ve seen buildings with columns that support nothing, with&amp;nbsp;purposeless cornices, with pilasters, moldings, false arches, false windows… Your house is made by its&amp;nbsp;own needs. The others are made by the need to impress. The determining motive of your house is in&amp;nbsp;the house. The determining motive of others is the audience.”&lt;/blockquote&gt;&lt;br /&gt;Keeping things simple and focused is hard work, just as agile development and YAGNI is not an excuse for sloppiness. &amp;nbsp;If done correctly, it should be quite the opposite. &amp;nbsp;In Roark's designs,&amp;nbsp;“Not a line seemed superfluous, not a needed plane was missing. The structures were austere and&amp;nbsp;simple, until one looked at them and realized &lt;i&gt;what work, what complexity of method, what tension of&amp;nbsp;thought had achieved the simplicity.&lt;/i&gt;” [emphasis added]&lt;br /&gt;&lt;br /&gt;This made me think of the quote in Uncle Bob's&amp;nbsp;&lt;a href="http://books.google.com/books/about/Clean_code.html?id=dwSfGQAACAAJ"&gt;Clean Code&lt;/a&gt;: "Learning to write clean code is hard work... You must sweat over it. You must practice it yourself, and watch yourself fail. You must watch others practice it and fail."&lt;br /&gt;&lt;br /&gt;There were some other ideas I'm hoping to examine in more depth:&lt;br /&gt;- right tool for the job - using technology idiomatically vs. legacy patterns with new technology&lt;br /&gt;- the architect as a hands-on practitioner (vs. ivory tower)&lt;br /&gt;- leveraging innovations - new methods, technologies etc.&lt;br /&gt;-&amp;nbsp;professional satisfaction / motivation&lt;br /&gt;- interactions with business stakeholders, "people skills" and organizational politics&lt;br /&gt;- making best of bad situations - looking for best possible solution even if you don't agree with the business problem to be solved&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-4773478234494441131?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/4773478234494441131/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=4773478234494441131' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/4773478234494441131'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/4773478234494441131'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/10/atlas-debugged-fountainhead-yagni-and.html' title='Atlas Debugged: The Fountainhead, YAGNI and &quot;clean code&quot;'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-4667360433213671403</id><published>2011-11-12T09:04:00.001-05:00</published><updated>2012-01-13T21:53:25.614-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='primefaces'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='jsf'/><title type='text'>Primefaces global AJAX events</title><content type='html'>You can use jQuery global AJAX events with PrimeFaces to refactor behaviors that appear on multiple components. &amp;nbsp;A good example is if you have a data grid with multiple p:commandLinks and other controls that execute different methods and re-render the grid, and need to run the same onComplete in all of them.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For example, if you start with something like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;pre class="brush: xml;"&gt;&amp;lt;h:panelGroup id="grid"&amp;gt;...&amp;lt;/h:panelGroup&amp;gt;&lt;br /&gt;&amp;lt;p:commandLink update="grid" actionListener="#{bean.update1}" onComplete="updateGrid()"/&amp;gt;&lt;br /&gt;&amp;lt;p:commandLink update="grid" actionListener="#{bean.update2}" onComplete="updateGrid()"/&amp;gt;&lt;br /&gt;&amp;lt;p:commandLink update="grid" actionListener="#{bean.update3}" onComplete="updateGrid()"/&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;You could pull the updateGrid() statement out into an AJAX listener like this&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;pre class="brush: js"&gt;jQuery(document).ajaxComplete(function(e, xhr, opts) {&lt;br /&gt;  $response = jQuery(xhr.responseXML);&lt;br /&gt;  if ($response.find("div[id$='grid']").length &amp;gt; 0) {&lt;br /&gt;     updateGrid();&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;pre&gt;&lt;span class="Apple-style-span" style="font-family: 'Times New Roman';"&gt;&lt;span class="Apple-style-span" style="white-space: normal;"&gt;By using ajaxComplete and parsing the XHR response object, you can see which DIV was going to be impacted by the partial update. &amp;nbsp;That way, if you had some other AJAX controls (say, an autocompleter) that you didn't want to trigger the updateGrid() function, you could filter that out. &amp;nbsp;Another option would be to set global=false on the specific Primefaces components that you don't want to fire the global jQuery ajaxComplete event. &amp;nbsp;The Primefaces autocompleter doesn't support this in Primefaces 2.x but does in 3.0. &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-4667360433213671403?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/4667360433213671403/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=4667360433213671403' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/4667360433213671403'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/4667360433213671403'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/11/primefaces-global-ajax-events.html' title='Primefaces global AJAX events'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-3846035398189162604</id><published>2011-11-08T20:13:00.002-05:00</published><updated>2012-01-13T21:53:51.537-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='primefaces'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='jsf'/><title type='text'>Primefaces p:ajax and jQuery AJAX events</title><content type='html'>All of Primefaces' JSF components use the jQuery AJAX engine, which means you can catch global AJAX events with $(document).ajaxStart and $(document).ajaxStop.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Standard JSF components like h:selectOneMenu will also go through jQuery when using a p:ajax facet (vs. standard f:ajax).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Conversely, jsf.ajax.onEvent does not appear to work with Primefaces components as it does for f:ajax. &amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;See also:&lt;/div&gt;&lt;div&gt;&lt;a href="http://stackoverflow.com/questions/6166352/fajax-and-jquerys-document-ajaxstart-dont-work-together"&gt;http://stackoverflow.com/questions/6166352/fajax-and-jquerys-document-ajaxstart-dont-work-together&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-3846035398189162604?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/3846035398189162604/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=3846035398189162604' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3846035398189162604'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3846035398189162604'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/11/primefaces-pajax-and-jquery-ajax-events.html' title='Primefaces p:ajax and jQuery AJAX events'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-165476981853438622</id><published>2011-11-08T13:08:00.000-05:00</published><updated>2011-11-08T13:08:18.787-05:00</updated><title type='text'>IE9 and strict JSON checking</title><content type='html'>Got an error from IE9 complaining about "invalid character".  Turned out this was because the JSON response string had control characters (newlines) in it.  These need to be escaped.&lt;br /&gt;&lt;br /&gt;Other browsers will let you get away with it, but IE9 is particularly strict and jQuery doesn't check the JSON response string before handing it off to JSON.parse.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;Similar issue found here:&lt;a href="https://mail.mozilla.org/pipermail/es-discuss/2010-June/011420.html"&gt;https://mail.mozilla.org/pipermail/es-discuss/2010-June/011420.html&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-165476981853438622?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/165476981853438622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=165476981853438622' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/165476981853438622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/165476981853438622'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/11/ie9-and-strict-json-checking.html' title='IE9 and strict JSON checking'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-2352121751565864812</id><published>2011-10-26T08:39:00.000-04:00</published><updated>2011-10-26T08:39:37.739-04:00</updated><title type='text'>Serialization, class hierarchy and preserving sessions</title><content type='html'>If you have a class that implements Serializable, superclass fields do NOT get serialized unless the parent class also explicitly implements Serializable.That bit me the other day with session-scoped objects and preserving sessions across restarts, using Glassfish 3.1.1.  Glassfish will complain if any objects in session scope or their properties don't implement serializable, but if those objects extend some parent class, the parent class fields are silently ignored and end up being null on session restore.  So the source of the problem wasn't immediately clear, like it would have been if individual session objects or properties within them weren't serializable.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-2352121751565864812?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/2352121751565864812/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=2352121751565864812' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/2352121751565864812'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/2352121751565864812'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/10/serialization-and-class-hierarchy.html' title='Serialization, class hierarchy and preserving sessions'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-836304170743336076</id><published>2011-10-22T19:54:00.001-04:00</published><updated>2011-10-22T19:54:46.976-04:00</updated><title type='text'>Quick and dirty SSO with LTPA</title><content type='html'>If you have WebSphere application server in your environment, it is in fact possible to decode the "LtpaToken" cookie in code for quick-and-dirty SSO with non-WebSphere apps.&lt;br /&gt;&lt;br /&gt;The main reason you might want to do this is if you have a portal-like application on WebSphere and want to link to other applications on different non-WebSphere servers. &amp;nbsp;This is only useful if WebSphere is your main point of entry.&lt;br /&gt;&lt;br /&gt;Here's how you do it:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Export the LTPA encryption key to a file from WebSphere using the admin console. &amp;nbsp;You provide a passphrase and a filename. &amp;nbsp;&lt;/li&gt;&lt;li&gt;Find the "com.ibm.websphere.ltpa.3DESKey" value in the exported file. &amp;nbsp;This is the encrypted key.&lt;/li&gt;&lt;li&gt;Base64 decode the above key and decrypt with 3DES, using the passphrase provided. &amp;nbsp;The decrypted value is the actual key for decrypting LTPA tokens.&lt;/li&gt;&lt;li&gt;Take the "LtpaToken" cookie, base64 decode it, and decrypt it with the key. &amp;nbsp;The legacy LtpaToken cookie (which you can get with "interoperability mode") is encrypted with 3DES; the newer LtpaToken2 cookie uses AES.&lt;/li&gt;&lt;li&gt;Convert to String and parse. &amp;nbsp;The string looks like "values%expiration%signature" where the expiration is a standard UNIX timestamp, which you should use to ensure the token is still valid; and the values somewhere will contain the user DN (e.g., uid=user,ou=company,dc=com).&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The Alfresco codebase contains a good example of how to do this in Java&lt;/div&gt;&lt;div&gt;&lt;a href="http://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/HEAD/root/modules/quickr/source/java/org/alfresco/repo/lotus/ws/impl/auth/LtpaAuthenticator.java"&gt;http://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/HEAD/root/modules/quickr/source/java/org/alfresco/repo/lotus/ws/impl/auth/LtpaAuthenticator.java&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Relevant fragments of code:&lt;/div&gt;&lt;pre style="white-space: pre-wrap; word-wrap: break-word;" class="brush: java"&gt;&lt;br /&gt;    private static final String AES_DECRIPTING_ALGORITHM = "AES/CBC/PKCS5Padding";&lt;br /&gt;   private static final String DES_DECRIPTING_ALGORITHM = "DESede/ECB/PKCS5Padding";&lt;br /&gt;&lt;br /&gt;    private byte[] getSecretKey(String ltpa3DESKey, String ltpaPassword) throws Exception&lt;br /&gt;    {&lt;br /&gt;        MessageDigest md = MessageDigest.getInstance("SHA");&lt;br /&gt;        &lt;br /&gt;        md.update(ltpaPassword.getBytes());&lt;br /&gt;        &lt;br /&gt;        byte[] hash3DES = new byte[24];&lt;br /&gt;        &lt;br /&gt;        System.arraycopy(md.digest(), 0, hash3DES, 0, 20);&lt;br /&gt;        &lt;br /&gt;        Arrays.fill(hash3DES, 20, 24, (byte) 0);&lt;br /&gt;        &lt;br /&gt;        final Cipher cipher = Cipher.getInstance(DES_DECRIPTING_ALGORITHM);&lt;br /&gt;        &lt;br /&gt;        final KeySpec keySpec = new DESedeKeySpec(hash3DES);&lt;br /&gt;        &lt;br /&gt;        final Key secretKey = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec);&lt;br /&gt;&lt;br /&gt;        cipher.init(Cipher.DECRYPT_MODE, secretKey);&lt;br /&gt;        &lt;br /&gt;        byte[] secret = cipher.doFinal(Base64.decodeBase64(ltpa3DESKey.getBytes()));&lt;br /&gt;        &lt;br /&gt;        return secret;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    private byte[] decrypt(byte[] token, byte[] key, String algorithm) throws Exception&lt;br /&gt;    {&lt;br /&gt;        SecretKey sKey = null;&lt;br /&gt;&lt;br /&gt;        if (algorithm.indexOf("AES") != -1)&lt;br /&gt;        {&lt;br /&gt;            sKey = new SecretKeySpec(key, 0, 16, "AES");&lt;br /&gt;        }&lt;br /&gt;        else&lt;br /&gt;        {&lt;br /&gt;            DESedeKeySpec kSpec = new DESedeKeySpec(key);&lt;br /&gt;            SecretKeyFactory kFact = SecretKeyFactory.getInstance("DESede");&lt;br /&gt;            sKey = kFact.generateSecret(kSpec);&lt;br /&gt;        }&lt;br /&gt;        Cipher cipher = Cipher.getInstance(algorithm);&lt;br /&gt;&lt;br /&gt;        if (algorithm.indexOf("ECB") == -1)&lt;br /&gt;        {&lt;br /&gt;            if (algorithm.indexOf("AES") != -1)&lt;br /&gt;            {&lt;br /&gt;                IvParameterSpec ivs16 = generateIvParameterSpec(key, 16);&lt;br /&gt;                cipher.init(Cipher.DECRYPT_MODE, sKey, ivs16);&lt;br /&gt;            }&lt;br /&gt;            else&lt;br /&gt;            {&lt;br /&gt;                IvParameterSpec ivs8 = generateIvParameterSpec(key, 8);&lt;br /&gt;                cipher.init(Cipher.DECRYPT_MODE, sKey, ivs8);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        else&lt;br /&gt;        {&lt;br /&gt;            cipher.init(Cipher.DECRYPT_MODE, sKey);&lt;br /&gt;        }&lt;br /&gt;        return cipher.doFinal(token);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    private IvParameterSpec generateIvParameterSpec(byte key[], int size)&lt;br /&gt;    {&lt;br /&gt;        byte[] row = new byte[size];&lt;br /&gt;        &lt;br /&gt;        for (int i = 0; i &amp;lt; size; i++)&lt;br /&gt;        {&lt;br /&gt;            row[i] = key[i];&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        return new IvParameterSpec(row);&lt;br /&gt;    }&lt;br /&gt;// How to use it:&lt;br /&gt;byte[] secretKey = getSecretKey(ltpa3DESKey, ltpaPassword);&lt;br /&gt;byte[] ltpaTokenBytes = Base64.decodeBase64(ltpaToken.getBytes());&lt;br /&gt;String token = new String(decrypt(ltpaTokenBytes, secretKey, DES_DECRIPTING_ALGORITHM));&lt;br /&gt;// or&lt;br /&gt;String token = new String(decrypt(ltpaTokenBytes, secretKey, AES_DECRIPTING_ALGORITHM));&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-836304170743336076?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/836304170743336076/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=836304170743336076' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/836304170743336076'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/836304170743336076'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/10/quick-and-dirty-sso-with-ltpa.html' title='Quick and dirty SSO with LTPA'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-7557090463342699895</id><published>2011-10-12T21:42:00.002-04:00</published><updated>2011-10-12T21:42:38.945-04:00</updated><title type='text'>Bizarre Glassfish JSF/EL performance issue</title><content type='html'>I found a performance issue with JSF on Glassfish 3.1.1 (and prior) in an unexpected place: a seemingly innocuous line of code like&lt;br /&gt;&lt;br /&gt;&amp;lt;h:outputText rendered="#{request.requestURL.indexOf('page') ne -1}" .... /&amp;gt;&lt;br /&gt;&lt;br /&gt;It turns out that invoking methods through EL expressions (new in EL 2.2) triggers creating a new ExpressionFactory, which in turn calls findResource/getResourceAsStream - a file I/O operation. &lt;br /&gt;&lt;br /&gt;These expressions in the "rendered" attribute are particularly bad because they get executed multiple times in various parts of the JSF lifecycle. &lt;br /&gt;&lt;br /&gt;See:&lt;br /&gt;&lt;a href="http://java.net/jira/browse/JAVASERVERFACES-2223"&gt;http://java.net/jira/browse/JAVASERVERFACES-2223&lt;/a&gt;&lt;br /&gt;&lt;a href="http://java.net/jira/browse/UEL-19"&gt;http://java.net/jira/browse/UEL-19&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;On the plus side - jvisualvm totally rocks. &amp;nbsp;I am shocked by how easy it was to get started with for tracking down these issues and it was right there in $JAVA_HOME/bin all along.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-7557090463342699895?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/7557090463342699895/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=7557090463342699895' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7557090463342699895'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7557090463342699895'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/10/bizarre-glassfish-jsfel-performance.html' title='Bizarre Glassfish JSF/EL performance issue'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-4812172292728148947</id><published>2011-10-11T21:33:00.001-04:00</published><updated>2011-10-11T21:33:19.522-04:00</updated><title type='text'>Infinite loop in Glassfish exception logging</title><content type='html'>When Glassfish catches a ServletException, it calls getRootCause recursively on each successive unwrapped, nested exception &lt;b&gt;by reflection&lt;/b&gt;. &amp;nbsp;This is fine for wrapped ServletException and JspException, which have well-defined semantics for this method, but creates a big problem if you have an application-defined exception that happens to use the same "getRootCause" name with different semantics. &lt;br /&gt;&lt;br /&gt;So I created a Glassfish bug report:&lt;br /&gt;&lt;a href="http://java.net/jira/browse/GLASSFISH-17390"&gt;http://java.net/jira/browse/GLASSFISH-17390&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Related original bug in Tomcat, since fixed:&lt;br /&gt;&lt;a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=39088"&gt;https://issues.apache.org/bugzilla/show_bug.cgi?id=39088&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-4812172292728148947?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/4812172292728148947/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=4812172292728148947' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/4812172292728148947'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/4812172292728148947'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/10/infinite-loop-in-glassfish-exception.html' title='Infinite loop in Glassfish exception logging'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-5755932689427525647</id><published>2011-09-25T09:45:00.000-04:00</published><updated>2011-09-25T09:45:19.180-04:00</updated><title type='text'></title><content type='html'>&lt;br /&gt;Connecting MicroStrategy 8.x Web SDK to a MicroStrategy 9.x Intelligence Server (iServer) doesn't work. &amp;nbsp;That in itself isn't a surprise. &amp;nbsp;What &lt;i&gt;is&lt;/i&gt;&amp;nbsp;surprising, though, is that the failure persists and then prevents the 8.x Web SDK from working with an 8.x iServer like it's supposed to on subsequent connections, resulting in an error "The required encryption level is not supported in this release."&lt;br /&gt;&lt;br /&gt;Apparently, this is a result of initializing "static" fields on the first session creation, such that those values get stuck for the lifetime of the classloader. &amp;nbsp;In this case, the connection to 9.x sets some of these values in ways that are incompatible with 8.x. &amp;nbsp;If the first session creation is to an 8.x iServer, then there's no problem - attempting to connect to 9.x will break but won't prevent subsequent connections to 8.x.&lt;br /&gt;&lt;br /&gt;This is an unusual edge case, to be sure. &amp;nbsp;I only encountered it during development in a heterogeneous environment, with a custom portal that connects to multiple iServers. &lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-5755932689427525647?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/5755932689427525647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=5755932689427525647' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/5755932689427525647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/5755932689427525647'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/09/connecting-microstrategy-8.html' title=''/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-3799416942236244374</id><published>2011-09-23T22:23:00.001-04:00</published><updated>2011-09-23T22:23:40.476-04:00</updated><title type='text'>solved windows 7 sleep problem</title><content type='html'>Sleep mode was grayed out on new Dell laptop (Latitude E6420) on Windows 7.&lt;br /&gt;&lt;br /&gt;The fix was to disable the legacy VGA driver, which came up with a yellow warning icon in Device Manager ("device cannot start"). &lt;br /&gt;&lt;br /&gt;Simply installing the latest Intel Graphics drivers did not fix the issue - the legacy VGA driver had to be explicitly disabled.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-3799416942236244374?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/3799416942236244374/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=3799416942236244374' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3799416942236244374'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3799416942236244374'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/09/solved-windows-7-sleep-problem.html' title='solved windows 7 sleep problem'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-9120185968415803544</id><published>2011-09-17T19:40:00.002-04:00</published><updated>2011-09-17T19:45:57.850-04:00</updated><title type='text'>502 Proxy Error solved</title><content type='html'>I was getting mysterious, intermittent proxy errors using Apache mod_proxy to talk to an older version of Glassfish (2.x).  It turned out the root cause was size of HTTP headers as cookies were accumulating, which caused the underlying app server to close the socket without a response.&lt;br /&gt;&lt;br /&gt;A similar phenomenon was documented here:&lt;br /&gt;&lt;a href="http://kenai.com/jira/browse/KENAI-2727"&gt;http://kenai.com/jira/browse/KENAI-2727&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The fix was trivial: increase the "receive-buffer-in-bytes" setting in domain.xml to 8192 bytes.&lt;br /&gt;&lt;br /&gt;Finding it was a pain, because the older version of Glassfish had a bug such that the underlying IOException that aborted the request never got logged anywhere!&lt;br /&gt;&lt;a href="http://java.net/jira/browse/GLASSFISH-5181"&gt;http://java.net/jira/browse/GLASSFISH-5181&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-9120185968415803544?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/9120185968415803544/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=9120185968415803544' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/9120185968415803544'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/9120185968415803544'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/09/502-proxy-error-solved.html' title='502 Proxy Error solved'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-5652208609672838476</id><published>2011-09-07T21:37:00.001-04:00</published><updated>2011-09-07T21:37:28.365-04:00</updated><title type='text'>JSF + JPA without EJB or Spring</title><content type='html'>I've had success using JSF 2.0 with JPA on Glassfish 3.0.1, without using Spring or EJB for managing middle-tier services or DAOs.  (I actually do like Spring - the goal in this case was to minimize dependencies, moving parts and learning curve, rather than to avoid any particular technology.)&lt;br /&gt;&lt;br /&gt;A similar approach was detailed here:&lt;br /&gt;&lt;a href="http://www.swview.org/blog/best-way-use-jpa-web-tier"&gt;http://www.swview.org/blog/best-way-use-jpa-web-tier&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;Using only the JSF @ManagedBean and @ManagedProperty annotations, along with the JPA @PersistenceContext, you can refactor business services or DAOs into other JSF managed beans that are injected into the one exposed directly to the UI.What this looks like in practice:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;orders.xhtml:&lt;br /&gt;&lt;pre class="brush:xml"&gt;&lt;h:datatable value="#{orderBean.orders}" var="order"&gt; &lt;br /&gt;&lt;/h:datatable&gt;&lt;/pre&gt;&lt;br /&gt;OrderBean.java:&lt;br /&gt;&lt;pre class="brush:java"&gt;@ManagedBean&lt;br /&gt;public class OrderBean {&lt;br /&gt;  @ManagedProperty(value="#{orderDao}")&lt;br /&gt;  private OrderDao orderDao;&lt;br /&gt;  private List&lt;order&gt; orders;&lt;br /&gt;   &lt;br /&gt;  @PostConstruct&lt;br /&gt;  public void init() { &lt;br /&gt;    this.orders = orderDao.getOrders();&lt;br /&gt;  } &lt;br /&gt;  public List&lt;order&gt; getOrders() { &lt;br /&gt;    return orders;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/order&gt;&lt;/order&gt;&lt;/pre&gt;&lt;br /&gt;OrderDao.java:&lt;br /&gt;&lt;pre class="brush:java"&gt;@ManagedBean&lt;br /&gt;@ApplicationScoped&lt;br /&gt;public class OrderDao {&lt;br /&gt;   @PersistenceContext(...)&lt;br /&gt;   private EntityManager em;&lt;br /&gt;   &lt;br /&gt;   public List&amp;lt;Order&amp;gt; getOrders() { &lt;br /&gt;     return em.findAll(Order.class);&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;In this example, the DAO (OrderDao) is just another JSF managed bean, but not actually referenced directly from a page, only from another managed bean.  This approach lets you isolate the logic tied to EntityManagers from the rest of your "normal" JSF managed beans, and makes it (marginally) easier to unit-test your managed beans because you can mock the DAO at a higher level instead of mocking the entity manager.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;This does NOT buy you declarative transactions or any of the other good stuff you get with Spring or EJB.  (You can inject a UserTransaction with @Resource, and call it explicitly, though.)  So it works best for simple apps with mostly read operations and basic single-object CRUD transactions.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;Also, with Java EE 6 CDI, all this may become moot because @Named and @Inject annotations effectively blur the line between all the different managed bean flavors (EJB, JSF, Spring/POJO), although I haven't found a good way to replace JSF @ViewScoped without buying into Seam.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-5652208609672838476?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/5652208609672838476/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=5652208609672838476' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/5652208609672838476'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/5652208609672838476'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/09/jsf-jpa-without-ejb-or-spring.html' title='JSF + JPA without EJB or Spring'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-7575721262538133771</id><published>2011-09-03T10:59:00.001-04:00</published><updated>2011-09-03T10:59:15.939-04:00</updated><title type='text'>WebSphere "invalid Oracle URL specified" error</title><content type='html'>&lt;br /&gt;Another unhelpful WebSphere error, this time with an assist from Oracle.&lt;br /&gt;&lt;br /&gt;This happened to me when I configured my JDBC data source with the default wpdbJDBC_oracle JDBC provider, using the XA datasource (OracleXADataSource), and used the "container managed" J2C authentication alias instead of "component managed". &amp;nbsp;The WebSphere admin console will successfully test the connection, but when you use it in a web application, it will fail with this "Invalid Oracle URL specified" error. &amp;nbsp;It was so hard to track down because it made me focus on the JDBC URL, which wasn't ever the problem. &amp;nbsp;It never occurred to me that the admin console and the web applications would somehow be getting connections and signing into Oracle differently, which tricked me into thinking that my configuration was really ok when it wasn't.&lt;br /&gt;&lt;br /&gt;For the record, the web application was just doing a straight JNDI datasource lookup without any resource-ref mapping in web.xml, using the same JNDI name as bound in the server.&lt;br /&gt;&lt;br /&gt;Also, changing to a different non-XA JDBC provider using plain OracleConnectionPoolDataSource resulted in "invalid username/password".&lt;br /&gt;&lt;br /&gt;When I changed the datasource to use the component-managed alias instead, and restarted, everything worked.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-7575721262538133771?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/7575721262538133771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=7575721262538133771' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7575721262538133771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7575721262538133771'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/09/websphere-invalid-oracle-url-specified.html' title='WebSphere &quot;invalid Oracle URL specified&quot; error'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-7264063111428714331</id><published>2011-08-30T10:24:00.001-04:00</published><updated>2011-08-30T10:28:18.831-04:00</updated><title type='text'>WebSphere Portal error - "Puma requested entity type Group from VMM but received Entity"</title><content type='html'>Got this error when trying to upgrade from WebSphere Portal 6.0 to 7.0.&lt;br /&gt;&lt;br /&gt;com.ibm.portal.puma.SchemaViolationException: EJPSG0053E: Puma requested entity type Group from VMM but received Entity.&lt;br /&gt;&lt;br /&gt;This sets a record for one of the most unhelpful errors ever.  It can be caused by a bad entry in LDAP - in my particular case, it was a few groups whose objectClasses (groupOfUniqueNames and top) had somehow been saved with base-64 encoding instead of as plain text, although the values themselves when decoded were correct.  The same exact LDAP directory was also working fine with WebSphere 6.0.&lt;br /&gt;&lt;br /&gt;One of IBM's pages implies that the configuration itself is broken - when in my case, the configuration was perfectly fine and the problem was a bad entry in LDAP.  The error gives no help tracking down the broken entry.&lt;br /&gt;&lt;br /&gt;https://www-304.ibm.com/support/docview.wss?uid=swg21419580&lt;br /&gt;&lt;br /&gt;Other IBM resources imply that the specific configuration issue is making sure that the group and user LDAP object classes are unique - i.e., don't use "top".  In my case, I was doing pretty standard stuff - groupOfUniqueNames and inetOrgPerson.&lt;br /&gt;&lt;br /&gt;http://publib.boulder.ibm.com/infocenter/wpzosdoc/v6r1/index.jsp?topic=/com.ibm.wp.msg.doc/messages/com.ibm.wps.puma.resources.Messages.html&lt;br /&gt;&lt;br /&gt;https://www-304.ibm.com/support/docview.wss?uid=swg1PK80507&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-7264063111428714331?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/7264063111428714331/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=7264063111428714331' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7264063111428714331'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/7264063111428714331'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/08/websphere-portal-error-puma-requested.html' title='WebSphere Portal error - &quot;Puma requested entity type Group from VMM but received Entity&quot;'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-3814469751662385242</id><published>2011-08-26T20:12:00.000-04:00</published><updated>2011-08-26T20:12:32.608-04:00</updated><title type='text'>Javascript, getYear and cookie expiration</title><content type='html'>Chrome and Firefox both return an offset from 1900 for date.getYear() instead of a four-digit year (pre-Y2K, a two-digit year).  So new Date().getYear() returns 111 instead of 2011.&lt;br /&gt;&lt;br /&gt;Old news, right?  Well, I've been getting away with this for a while and not realizing it, in logic to expire cookies, and didn't notice until switching to Chrome.&lt;br /&gt;&lt;br /&gt;The cookie expiration function was something like this&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:js"&gt;Date d = new Date();&lt;br /&gt;d.setYear(d.getYear() - 1); // last year = 110&lt;br /&gt;document.cookie = "COOKIE_NAME=; expires=" + d.toGMTString();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Firefox was happily setting the cookie's expiration to the year 0110, which was wrong but still accomplishing the end goal of expiring the cookie.  &lt;br /&gt;&lt;br /&gt;Chrome, on the other hand, mangled toGMTString to only print the three digits "110" and was not interpreting the cookie as expired.&lt;br /&gt;&lt;br /&gt;Of course, the better question is why write this code yourself anyway, when jQuery, Dojo, etc., all have their own cookie APIs. &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-3814469751662385242?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/3814469751662385242/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=3814469751662385242' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3814469751662385242'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/3814469751662385242'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/08/javascript-getyear-and-cookie.html' title='Javascript, getYear and cookie expiration'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-2739718696717629661</id><published>2011-08-24T09:31:00.000-04:00</published><updated>2011-08-24T09:31:06.591-04:00</updated><title type='text'>Javascript associative array and iteration order</title><content type='html'>I just noticed that some browsers, like IE9 and Chrome, don't preserve insertion order of keys when iterating with a for-in loop ["for (key in array)"].  Although technically Javascript never guaranteed preserving order in a for-in loop, so many popular browsers did anyway that I was taken by surprise.&lt;br /&gt;&lt;br /&gt;The Chrome behavior is especially odd - it preserves order of insertion for non-numeric keys, but iterates numeric keys in numeric order first.  It also coerces numeric strings to numbers.&lt;br /&gt;&lt;br /&gt;This code example illustrates&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:js"&gt;var foo=new Object();&lt;br /&gt;&lt;br /&gt;foo["111_"] = 1;&lt;br /&gt;foo["222_"] = 2;&lt;br /&gt;foo["333_"] = 3;&lt;br /&gt;foo["444"] = 4;&lt;br /&gt;foo["555"] = 5;&lt;br /&gt;foo["666"] = 6;&lt;br /&gt;&lt;br /&gt;var str = "";&lt;br /&gt;for (var key in foo) { &lt;br /&gt;  str = str + foo[key] + " ";&lt;br /&gt;}&lt;br /&gt;document.write(str);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In Chrome this prints "4 5 6 1 2 3".  In IE 7, IE 8, and Firefox, it prints "1 2 3 4 5 6".&lt;br /&gt;&lt;br /&gt;This behavior (particularly the coercion of numeric strings) has been tracked as a bug in Chrome - but unclear whether it will ever be fixed&lt;br /&gt;&lt;a href="http://code.google.com/p/chromium/issues/detail?id=37404"&gt;http://code.google.com/p/chromium/issues/detail?id=37404&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;ECMAScript spec says "order of enumerating... is not defined"&lt;br /&gt;&lt;a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf"&gt;http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-2739718696717629661?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/2739718696717629661/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=2739718696717629661' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/2739718696717629661'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/2739718696717629661'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2011/08/javascript-associative-array-and.html' title='Javascript associative array and iteration order'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-111909637844779466</id><published>2005-06-18T07:52:00.000-04:00</published><updated>2005-06-18T08:13:23.706-04:00</updated><title type='text'>commons-logging classloader pain</title><content type='html'>I recently learned about commons-logging's classloader behavior the hard way when moving applications that use log4j from Tomcat to WebLogic.  I had naively put commons-logging.jar in my WebLogic system classpath to emulate Tomcat's behavior, and things subsequently broke.  This web site provided a very detailed and erudite explanation:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.qos.ch/logging/classloader.jsp"&gt;http://www.qos.ch/logging/classloader.jsp&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Basically, it boils down to weird subtlety in commons-logging's behavior.  If commons-logging.jar is in the system classpath in a J2EE environment, the "current" or "thread context" classloader for each individual webapp contains WEB-INF/lib, but commons-logging itself is loaded in the system (parent) classpath.  commons-logging looks for log4j using the thread-context classloader and therefore will "see" log4j in WEB-INF/lib, but tries to actually load it in the system classpath, and breaks with a NoClassDefFoundError.    &lt;br /&gt;&lt;br /&gt;Various solutions:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;Only put commons-logging-api.jar in the system classpath, as Tomcat does.  (I didn't realize there were two different variations of the JAR at first.  I know, RTFM.)  This stripped-down commons-logging will  pretend that log4j doesn't exist.  Downside: logging output from third-party APIs like Hibernate, Digester, etc. will go who-knows-where, but not the same place as your application's log4j output.&lt;br /&gt;&lt;br /&gt;  &lt;li&gt;Put log4j in the system classpath too.  This works, but has the bad side effect that all applications will share a single log4j configuration and this makes logging unmanageable.&lt;br /&gt;&lt;br /&gt;  &lt;li&gt;Put commons-logging.jar in your WEB-INF/lib directory.  I'd heard bad things about this, especially if you want to deploy the same WAR in Tomcat.  But for whatever reason it worked fine with Tomcat 4.1.31 and WebLogic 8.1.  So this may be my preferred solution.&lt;br /&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Anyhow, if this was confusing to someone who has been doing J2EE professionally for 5-6 years, I can imagine how confusing it must be to someone just trying to do this for the first time.  The &lt;a href="http://www.jroller.com/page/fate/?anchor=the_evils_of_commons_logging"&gt;BileBlog&lt;/a&gt; puts it a lot less charitably.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-111909637844779466?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/111909637844779466/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=111909637844779466' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/111909637844779466'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/111909637844779466'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2005/06/commons-logging-classloader-pain_18.html' title='commons-logging classloader pain'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-111788532438791258</id><published>2005-06-04T07:17:00.000-04:00</published><updated>2005-06-04T07:46:56.886-04:00</updated><title type='text'>cost of supporting old browsers</title><content type='html'>I've begun to question the wisdom of standards for websites that require support for older browsers like Netscape 4. Backwards compatibility is all well and good, but I feel like often times clients--government ones in particular--forumulate these requirements without any real cost-benefit analysis.&lt;br /&gt;&lt;br /&gt;Keeping support for Netscape 4, IE 3, etc. has a very real cost, with respect to CSS and DHTML/Javascript one-offs, or having to pass on new patterns like AJAX. But many statistics show that fewer than 1% of all browsers are Netscape 4--even going back a year or two. (See &lt;a href="http://www.w3schools.com/browsers/browsers_stats.asp"&gt;here&lt;/a&gt; for an example.) Most people are using IE 6.&lt;br /&gt;&lt;br /&gt;On the other hand, a commercial client I visited had done a very careful cost-benefit analysis on operating system support--they knew exactly how many of their users were still using Windows 98 and how much it would tick them off if they were forced to upgrade. So they might be stuck supporting it, but at least they could justify the decision.&lt;br /&gt;&lt;br /&gt;So, before making requirements to support Netscape 4, clients should do their homework, and ask themselves what the benefit is--is anyone really still using it and can't upgrade? And how much extra time and money are the developers going to spend on workarounds to support these old browsers, compared to the value delivered?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-111788532438791258?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/111788532438791258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=111788532438791258' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/111788532438791258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/111788532438791258'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2005/06/cost-of-supporting-old-browsers.html' title='cost of supporting old browsers'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110764084375738137</id><published>2005-02-05T16:46:00.000-05:00</published><updated>2005-02-05T17:00:43.756-05:00</updated><title type='text'>Wireless adventure</title><content type='html'>I must have bad luck with my home wireless network.   I had a D-Link DI-514 wireless router, which worked great until I got a new IBM Thinkpad X40 with the built-in Centrino.  After various firmware upgrades, configuration changes, etc., I could not get the two to work together--I was getting roughly 30% packet loss and 500 ms ping times, with the notebook right next to the router antenna.&lt;br /&gt;&lt;br /&gt;So I got a new DI-524 which had a big Centrino-verified logo on the front.  But, after unpacking it and hooking it up, it won't talk to my cable modem (also a D-Link product, a DCM-200) and the "WAN" link light doesn't even come on. &lt;br /&gt;&lt;br /&gt;I take the thing back to the store, bring home another DI-524 *and* a Linksys BEFW11S4 just to be safe.   Sure enough, they *all* have the same problem with the WAN link light.  What are the odds?&lt;br /&gt;&lt;br /&gt;After some googling and a call to D-Link tech support (who was way more knowledgeable than I expected) it turns out that this mass-market consumer equipment doesn't do a good job of auto-sensing 10- vs. 100-mbit ethernet, and my old cable modem is only 10mb.  After a firmware upgrade it all worked. &lt;br /&gt;&lt;br /&gt;The design lesson here is the "principle of least astonishment"--I'm somewhat knowledgeable about network stuff, but so far every piece of 100mbit ethernet equipment I've used doesn't just break if the other end is only 10mbit.   So when that link light didn't come on, I immediately assumed the unit was defective, especially when there was no documentation of this issue.  I could have saved myself (and Best Buy's returns counter) a whole lot of trouble if the problem were more clear.    &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110764084375738137?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110764084375738137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110764084375738137' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110764084375738137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110764084375738137'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2005/02/wireless-adventure.html' title='Wireless adventure'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110579412277236329</id><published>2005-01-15T07:55:00.000-05:00</published><updated>2005-01-15T08:07:02.296-05:00</updated><title type='text'>displaytag sorting/paging performance vs. native SQL</title><content type='html'>displaytag is an easy-to-use JSP tag library for printing out HTML data tables with sorting and paging. But, there's a catch. In an enterprise application, collections are the result of some database query, so implementing sorting and paging entirely within the presentation layer is not going to be as efficient as doing both natively in the database with ORDER BY, LIMIT and OFFSET (assume PostgreSQL syntax). Instead, displaytag expects the entire collection to be in memory, displaying only the necessary ones, and sorting the Java collection itself.&lt;br /&gt;&lt;br /&gt;I ran some tests to see exactly how much this affects performance. On a non-scientific test (YMMV) with a 250 row result set, sorting with displaytag added an 34% overhead over a native ORDER BY. There was a much more dramatic difference for paging, though. On a test where I loaded 250 rows from the DB and displayed only 25 (pagesize=25), displaytag took 141% longer on average than a "LIMIT 25" which only returns the 25 rows to actually be displayed. The overhead factor will scale with the total number of rows in the original, unlimited query.&lt;br /&gt;&lt;br /&gt;So, displaytag clearly is not as efficient as sorting and paging in native SQL. But is it enough of a difference to matter? Well, it depends on your application--it's a classic tradeoff between performance and elegant design.  Separation of concerns says paging results &lt;em&gt;conceptually&lt;/em&gt; belongs in the presentation layer; in a layered J2EE application, you can push sorting/paging down through the business and persistence layers, but it isn't pretty.&lt;br /&gt;&lt;br /&gt;My personal opinion, though, is that performance normally shouldn't be enough of a factor to dissuade you from using displaytag for sorting/paging results. If your query returns so many rows that you can't afford the overhead from the undisplayed rows, then you may not be thinking about paging the right way--paging should be thought of as purely a UI layer construct to save the user from scrolling or downloading a large HTML document, not to save the database from working too hard. How meaningful is it to jump from page 1 to page 47 out of 62 anyway? If your query returns more than, say, 300 rows (15 pages with 20 rows each), you probably should think about making the user provide additional search criteria first rather than blindly paging the output.&lt;br /&gt;&lt;br /&gt;What about the other option--coupling the presentation layer directly to the database? It's easy to imagine an extension to displaytag that takes a SQL query rather than a Java collection, and handles sorting/paging natively by dynamically appending ORDER BY and LIMIT/OFFSET clauses. (Indeed, the MS toolset seems to actively encourage this pattern.)&lt;br /&gt;&lt;br /&gt;This is fine for prototyping or simple apps that don't have much business logic beyond the basic CRUD transactions. But, this can rapidly become unmaintainable--not only are there many more places to touch if the DB schema changes, but you are also at risk of introducing bugs by accessing tables directly and potentially bypassing domain logic (business rules) tied to particular fields. Still, it may be worth considering tihs as a strategy for hand-optimizing queries with special performance requirements that outweigh maintainability, especially if the query is more relational than object-oriented in nature.&lt;br /&gt;&lt;br /&gt;In summary, displaytag is a good, simple choice for many applications that need HTML tables with sorting and paging. There may be a performance hit, but a clean, maintainable design often is more&lt;br /&gt;important.    &lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110579412277236329?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110579412277236329/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110579412277236329' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110579412277236329'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110579412277236329'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2005/01/displaytag-sortingpaging-performance.html' title='displaytag sorting/paging performance vs. native SQL'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110527850632268878</id><published>2005-01-09T08:48:00.000-05:00</published><updated>2005-01-09T09:00:19.093-05:00</updated><title type='text'>USB flash drives and Linux</title><content type='html'>I've had to set up a USB stick with Linux recently.  It's fairly easy if you know the right magic.&lt;br /&gt;&lt;br /&gt;Here are some links to useful resources:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.extremetech.com/article2/0,1558,1256766,00.asp"&gt;Post on ExtremeTech.com&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.ibiblio.org/pub/Linux/docs/HOWTO/other-formats/html_single/Flash-Memory-HOWTO.html"&gt;Flash memory HOWTO on ibiblio&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Essentially it boils down to adding a line to /etc/fstab and then mounting /dev/sda1 or /dev/sdb1 on some mount point (directory). You don't strictly need to edit /etc/fstab, but if you don't you will need to be root in order to write anything on it. Here's the magic for /etc/fstab to allow non-root to get read/write access:&lt;br /&gt;&lt;font&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;&lt;pre class="screen"&gt;/dev/sda1 /mnt/usbstick vfat        rw,user,noauto 0 0&lt;br /&gt;&lt;/pre&gt;&lt;/span&gt;&lt;/font&gt;&lt;/span&gt;(assuming your mount point is /mnt/usbstick.)&lt;br /&gt;&lt;br /&gt;If that doesn't work, check /var/log/messages and see if your stick is on /dev/sdb1 instead.&lt;br /&gt;&lt;br /&gt;I'm looking forward to it being as easy to use USB sticks under a default Linux distro as it is under Windows, though. I hate to say it, but under Windows, they "just work."&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110527850632268878?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110527850632268878/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110527850632268878' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110527850632268878'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110527850632268878'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2005/01/usb-flash-drives-and-linux.html' title='USB flash drives and Linux'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110458868731734186</id><published>2005-01-01T08:36:00.000-05:00</published><updated>2005-01-01T21:02:40.196-05:00</updated><title type='text'>Avoiding Anemic Domain Models with Hibernate</title><content type='html'>One of Hibernate's most under-appreciated features is its &lt;a href="http://www.hibernate.org/hib_docs/reference/en/html/persistent-classes.html#persistent-classes-pojo-accessors"&gt;ability to persist private fields.&lt;/a&gt; This feature is useful for avoiding what Martin Fowler calls the &lt;a href="http://www.martinfowler.com/bliki/AnemicDomainModel.html"&gt;Anemic Domain Model&lt;/a&gt; anti-pattern, where domain objects (entities) are reduced to "dumb" record structures with no business logic. In an Anemic Domain Model, you lose all the benefits of OOP: polymorphism, data hiding, encapsulation, etc.&lt;br /&gt;&lt;br /&gt;The Anemic Domain Model may have originally evolved from EJB CMP, which requires any persistent field to be accessible directly with a public getter/setter. Developers using POJO frameworks like Hibernate often duplicate the same pattern, though, simply replacing the entity beans with POJOs.&lt;br /&gt;&lt;br /&gt;This is not just an academic discussion; this has real consequences for the quality of a codebase. (Academically, this is part of the OOP-RDBMS "impedance mismatch"--in particular, that there is no distinction between a setter/constructor call that actually mutates/constructs an object and one that is merely incidental to materializing an existing object's state from persistent storage.) Let's say you're developing a system for issue tracking with a business rule like "anyone can create a ticket or change its status, but only managers can raise it to 'critical.'" A fragment of an Issue object might look like this (some detail omitted to focus on encapsulation/data hiding issues):&lt;br /&gt;&lt;pre&gt;public class Issue {&lt;br /&gt;private String m_status;&lt;br /&gt;public String getStatus() {&lt;br /&gt;  return m_status;&lt;br /&gt;}&lt;br /&gt;public void setStatus(String newStatus) {&lt;br /&gt;  if (newStatus == STATUS_CRITICAL &amp;&amp;amp; !getCurrentUser().isManager()) {&lt;br /&gt;    throw new SecurityException("critical.requires.manager");&lt;br /&gt;  }&lt;br /&gt;  m_status = newStatus;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;This looks great until you realize that setStatus(STATUS_CRITICAL) is also going to be called from the persistence layer in materializing an existing Issue that is &lt;span style="font-style: italic;"&gt;already &lt;/span&gt;critical, not just when making an explicit change through the UI workflow. Since anyone can view any issue, SecurityException will be thrown when a non-manager tries to view an issue that is already critical. We immediately recognize that the persistence layer needs a way to get "privileged" access to set the underlying field directly, bypassing business logic.&lt;br /&gt;&lt;br /&gt;The typical workaround is to give up encapsulation and move the business logic into the corresponding service layer object (e.g., stateless session bean) for issue transactions:&lt;br /&gt;&lt;pre&gt;public class IssueManager {&lt;br /&gt;public Issue findIssueById(Long id) ;&lt;br /&gt;public Issue newIssue(... fields ...) {&lt;br /&gt;  // begin TX&lt;br /&gt;  // ... setup new issue&lt;br /&gt;  if (status == STATUS_CRITICAL &amp;&amp;amp; !getCurrentUser().isManager()) {&lt;br /&gt;     throw new SecurityException("critical.requires.manager");&lt;br /&gt;  }&lt;br /&gt;  issue.setStatus(status);&lt;br /&gt;  // ...&lt;br /&gt;  // commit TX&lt;br /&gt;}&lt;br /&gt;public void changeStatus(Long id, String status) {&lt;br /&gt;  // begin TX, load issue&lt;br /&gt;  if (status == STATUS_CRITICAL &amp;&amp;amp; !getCurrentUser().isManager()) {&lt;br /&gt;     throw new SecurityException("critical.requires.manager");&lt;br /&gt;  }&lt;br /&gt;  issue.setStatus(status);&lt;br /&gt;  // commit TX&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Now, two real consequences are apparent.  First, giving up encapsulation leads to cut-and-paste programming, violating the &lt;a href="http://www.pragmaticprogrammer.com/ppbook/extracts/rule_list.html"&gt;"don't repeat yourself" principle&lt;/a&gt;; this increases the risk of error of the business rule not being cut-and-paste again somewhere it's needed. Second, you lose polymorphism; it is now very difficult to have a subclass of Issue with slightly different business rules. (For example, maybe the main Issue has no restriction on setting status, but a specific type of issue has the critical-requires-manager rule.)&lt;br /&gt;&lt;br /&gt;It's true that you could have two separate sets of getters/setters in the Issue itself, one that applies business logic and one that allows direct access and is only used by persistence. This would address the polymorphism issue. But if that direct accessors are also public (as EJB CMP requires) then you still lose data hiding; nothing prevents your service layer/transaction scripts from calling these methods directly.&lt;br /&gt;&lt;br /&gt;If you're using Hibernate, though, there is a very elegant solution. Hibernate is effectively "privileged" by manipulating bytecode, so it can touch private fields directly. Hibernate gives you two options in the above scenario:&lt;br /&gt;&lt;ul&gt;   &lt;li&gt;You can have two separate bean-style properties linked to the same underlying field, one with private getters/setters and the other with public. The private methods access the underlying field directly, and the public ones apply business rules. This is the preferred approach, but has the downside of verbosity, plus you have to use different property names in HQL (private) and everywhere else (public).&lt;br /&gt; &lt;/li&gt;   &lt;li&gt;Hibernate can also persist fields directly by using the &lt;a href="http://www.hibernate.org/hib_docs/reference/en/html/mapping.html#mapping-declaration-property"&gt;"access" attribute on @hibernate.property&lt;/a&gt; and so on. The upside is that this is more concise with only a single public bean-style property, but using access="field" requires the field name to exactly match the private instance variable name; this won't work if you have some kind of Hungarian naming convention like "m_foo". You can do something like access="MyFieldAccessor" where MyFieldAccessor is a custom class implementing &lt;tt class="literal"&gt;net.sf.hibernate.property.PropertyAccessor&lt;/tt&gt;, implementing your naming convention (mapping bean property names to member var names) but that requires extra effort.&lt;/li&gt; &lt;/ul&gt; There are other uses for this feature in Hibernate:&lt;br /&gt;&lt;ul&gt;   &lt;li&gt;Primary keys are generally supposed to be immutable by normal business logic, set only within the persistence layer. So, "setId" methods can almost always be private or protected.&lt;br /&gt; &lt;/li&gt;   &lt;li&gt;Collections getters and setters can also be kept private, to preserve data hiding (prevent rep exposure). Otherwise, when business logic can manipulate a collection directly, it's difficult to enforce business rules on the collection elements, or even to ensure the elements are of the correct type. (The latter may partially be addressed by generics in Java 5 and/or Hibernate 3.)&lt;br /&gt; &lt;/li&gt; &lt;/ul&gt; I believe JDO also instruments classes at runtime to get similar privileged access to persistent fields.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110458868731734186?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110458868731734186/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110458868731734186' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110458868731734186'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110458868731734186'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2005/01/avoiding-anemic-domain-models-with.html' title='Avoiding Anemic Domain Models with Hibernate'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110376825741051112</id><published>2004-12-22T21:07:00.000-05:00</published><updated>2004-12-22T21:17:37.410-05:00</updated><title type='text'>more PostgreSQL performance junk</title><content type='html'>Someone at work was running a big delete (100k rows) and it was taking forever, as if it were hung.  We couldn't figure out what was going on, and there was clearly a non-linear effect: smaller deletes on 10k rows were completing very fast, less than 5 sec, while 100k rows was still running after 20 mins. &lt;br /&gt;&lt;br /&gt;We think the big delete was taking so long because PostgreSQL may try to keep a rollback buffer for the whole delete operation in memory or something like that, causing thrashing or something like that.   I haven't tried this on Oracle, but I'm guessing that it and other databases may be smarter about managing their physical storage directly (RAM vs disk) rather than relying on the underlying OS. &lt;br /&gt;&lt;br /&gt;but then again, if you're touching 100k rows at once, probably not a bad idea to commit every so often anyway, so as to avoid a long-running transaction that could potentially hose other users.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110376825741051112?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110376825741051112/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110376825741051112' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110376825741051112'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110376825741051112'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2004/12/more-postgresql-performance-junk.html' title='more PostgreSQL performance junk'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110376764325457999</id><published>2004-12-22T21:02:00.000-05:00</published><updated>2004-12-22T21:07:23.256-05:00</updated><title type='text'>PostgreSQL on cygwin: "Bad system call"</title><content type='html'>I was getting this "Bad system call" message from PostgreSQL 7.4.x on cygwin.  It was working earlier, and I thought I had done everything right--cygserver was up and running, so I didn't know what was up.   ipcs gave the same error.&lt;br /&gt;&lt;br /&gt;Turns out I had forgot the magic word: "CYGWIN=server".  The first time I installed PostgreSQL (and read the docs), I had just set the var on the command line (CYGWIN=server pg_ctl start ...)  and never put it in my profile.  Easy enough to fix. &lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110376764325457999?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110376764325457999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110376764325457999' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110376764325457999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110376764325457999'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2004/12/postgresql-on-cygwin-bad-system-call.html' title='PostgreSQL on cygwin: &quot;Bad system call&quot;'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110317254344413332</id><published>2004-12-15T22:34:00.000-05:00</published><updated>2004-12-15T23:56:11.886-05:00</updated><title type='text'>PostgreSQL performance of "where exists" </title><content type='html'>Today I was looking into the performance of a PostgreSQL query with a "... where exists (select 1 from ...)" subquery:&lt;br /&gt;&lt;pre&gt;select foo_id from foo&lt;br /&gt;where exists (select 1 from bar where bar.foo_id = foo.foo_id)&lt;br /&gt;&lt;/pre&gt;I was surprised to find out that this query actually ran &lt;i&gt;faster&lt;/i&gt; when I restructured it with a SELECT DISTINCT and a JOIN:&lt;br /&gt;&lt;pre&gt;select distinct(foo_id) from bar&lt;br /&gt;join foo on bar.foo_id=foo.foo_id&lt;br /&gt;&lt;/pre&gt;Some references on the web I've found suggest that EXISTS is the &lt;a href="http://www.databasejournal.com/features/mssql/article.php/1438631"&gt;preferred way to write the above query in general&lt;/a&gt;. Because it's a boolean condition, in theory the database needs to scroll fewer rows because it can stop as soon as the first match is found; and the DISTINCT can be expensive if the results from the join version would not have been unique.&lt;br /&gt;&lt;span style="text-decoration: underline;"&gt;&lt;br /&gt;&lt;/span&gt;An &lt;a href="http://archives.postgresql.org/pgsql-general/2001-02/msg00667.php"&gt;ancient PostgreSQL mailing list post&lt;/a&gt; indicates that rewriting the query as a JOIN may be faster than EXISTS in PostgreSQL, because the join can take advantage of indexes while EXISTS does a nested loop. But, then again, I'm still using PostgreSQL 7.3.x, and EXISTS handling may well have been improved in 7.4.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110317254344413332?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110317254344413332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110317254344413332' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110317254344413332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110317254344413332'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2004/12/postgresql-performance-of-where-exists.html' title='PostgreSQL performance of &quot;where exists&quot; '/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9159309.post-110278493594363125</id><published>2004-12-11T12:07:00.000-05:00</published><updated>2004-12-11T12:11:31.320-05:00</updated><title type='text'>"Client CVS Branch" anti-pattern</title><content type='html'>  &lt;p class="MsoNormal"&gt;Scenario: Your team developed a custom application for Client A.&lt;span style=""&gt;  &lt;/span&gt;The application is generally useful, so it gets re-sold to Client B.&lt;span style=""&gt;  &lt;/span&gt;Client B wants some customizations, which are at first superficial (CSS, images, etc.), and the client expects a quick turnaround.&lt;span style=""&gt;  &lt;/span&gt;So, you need a way to store Client B’s new version of the app in source control somehow, and you take the first approach that comes to mind: you create a branch of the original, and make the customizations for Client B on the new branch.&lt;span style=""&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt; (Without loss of generality, I’m assuming you’re using CVS; Subversion and Perforce have different branch/merge models but they basically boil down to the same thing.&lt;span style=""&gt;  &lt;/span&gt;But if you’re using PVCS or VSS, or worse, not using source control at all, you have bigger problems which I’ll save for some other time.)&lt;span style=""&gt; &lt;/span&gt; &lt;p class="MsoNormal"&gt;At first, this works pretty well.&lt;span style=""&gt;  &lt;/span&gt;But over time, you sell the same app to more clients, and they are each asking for more substantial new feature development.&lt;span style=""&gt;  &lt;/span&gt;The strategy starts to break down, causing a whole series of problems:&lt;/p&gt; &lt;p class="MsoNormal" style="margin-left: 0.5in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;!--[endif]--&gt;- Bug fixes have to be explicitly merged into each client’s branch individually. &lt;/p&gt;   &lt;p class="MsoNormal" style="margin-left: 0.5in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;-&lt;span style=""&gt;         &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Since the code in each client branch diverges over time as different things are added or changed in one or the other, merging bug fixes results in more manual conflict resolution.&lt;span style=""&gt;  &lt;/span&gt;(The same thing goes for new features.)&lt;span style=""&gt;   &lt;/span&gt;This also makes for more re-testing of the same thing. &lt;/p&gt;   &lt;p class="MsoNormal" style="margin-left: 0.5in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;-&lt;span style=""&gt;         &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;There is a potential for wheel reinvention.&lt;span style=""&gt;  &lt;/span&gt;If you develop a new feature for Client C, and Client D asks for a new feature that is “close but not quite” the same as Client C’s, it may be developed independently twice rather than building it once in a way that accommodates both sets of requirements.&lt;span style=""&gt;  &lt;/span&gt;&lt;/p&gt;   &lt;p class="MsoNormal" style="margin-left: 0.5in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;-&lt;span style=""&gt;         &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;You fail to realize potential economies of scale of in support or maintenance that you should get from having a single solution, since each client effectively has their own one-off version.&lt;/p&gt; &lt;p class="MsoNormal"&gt;The problem stems from the lack of a well-defined “trunk” in source control that provides the common baseline functionality.&lt;span style=""&gt;  &lt;/span&gt;Instead, each client’s version was branching off of &lt;i style=""&gt;another client’s &lt;/i&gt;branch (Client A’s) rather than from a common trunk.&lt;span style=""&gt;   &lt;/span&gt;So there was no way to nail down which part of the code stays constant for all clients.&lt;span style=""&gt;  &lt;/span&gt;&lt;/p&gt;     &lt;p class="MsoNormal"&gt;Here's a few ways to solve this problem:&lt;span style=""&gt; &lt;/span&gt;&lt;/p&gt;   &lt;p class="MsoNormal" style="margin-left: 0.5in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;-&lt;span style=""&gt;         &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Have each client’s version be a branch from a common trunk, and have the discipline to make as much functionality in the trunk configurable at deployment/runtime as possible (the later the binding, the better).&lt;span style=""&gt;  &lt;/span&gt;That way, you increase the percentage of code that all clients have in common, and establish a common baseline version that multiple clients share.&lt;span style=""&gt;  &lt;/span&gt;Also, there will then be a well-defined process for upgrading a client’s branch to a new version of the baseline “core” code, and many fewer post-merge conflicts to resolve manually.&lt;/p&gt; &lt;p class="MsoNormal" style="margin-left: 0.5in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;-&lt;span style=""&gt;         &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Most teams won’t actually have the discipline to consistently put in the extra effort to make new features configurable.&lt;span style=""&gt;  &lt;/span&gt;So you can take this a step further: give each client is own separate repository that contains &lt;i style=""&gt;only &lt;/i&gt;the customizations for that client, then apply those customizations as a patch against the common baseline version.&lt;span style=""&gt;  &lt;/span&gt;This will force you to think about whether any given code should be common and shared across all clients or whether it is a customization.&lt;span style=""&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt; &lt;p class="MsoNormal" style="margin-left: 0.5in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;-&lt;span style=""&gt;         &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;There is also a management aspect to solving this problem: make sure someone is accountable for the entire solution as deployed for &lt;i style=""&gt;all&lt;/i&gt; clients, not just each individual client’s project.&lt;span style=""&gt;  &lt;/span&gt;When you’re only accountable for your own client, you’ll inevitably take the path of least resistance to keep your own client happy and not see the bigger picture of delivering for all clients more effectively (this is actually &lt;i style=""&gt;rational&lt;/i&gt; behavior according to game theory).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9159309-110278493594363125?l=wrschneider.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wrschneider.blogspot.com/feeds/110278493594363125/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9159309&amp;postID=110278493594363125' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110278493594363125'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9159309/posts/default/110278493594363125'/><link rel='alternate' type='text/html' href='http://wrschneider.blogspot.com/2004/12/client-cvs-branch-anti-pattern.html' title='&quot;Client CVS Branch&quot; anti-pattern'/><author><name>Bill Schneider</name><uri>http://www.blogger.com/profile/10473101381793442268</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
