zarar

9 posts

The problem at hand is that EclipseLink (great project lead by James Sutherland) does not use a query cache when dealing with ReadAll queries, i.e: all calls togetResultList() go to the database.  Some object-level caching is performed by avoiding construction of new objects based on the primary key values the database call returns.  EclipseLink compares the PK values returned by thegetResultList() query to that in its identity cache and if matches are found, the cached objects are returned.  After running JProfiler, I determined that the saving weren't really significant at all as the query was being executed every time and only entity creation was avoided.  

I realize that caching calls from getResultList()can be dangerous as changes from other applications will not be reflected, and that is the argument I received on the mailing list.  The argument carries merit, but since the default behavior of EclipseLink is to maintain an identity cache as described in the previous paragraph, any outside changes to non-primary key values will not be reflected anyway, so I don't see what the big deal about caching ReadAll queries really is. EclipseLink can't cache native queries since they aren't mapped nicely to JPA@Entity objects.  Since I'm working on a legacy system with a database design that resembles spaghetti and meatballs which also happens to be heavily used, I desperately needed to cache some queries that I know return fairly static data.

Anyway, so I went about trying to implement a cache and thankfully, EclipseLink provides a great extension point through query redirectors.  Basically, specifying a QueryRedirector on a query will allow you to control the logic right before query execution.   The @QueryRedirectors annotation has a bug which forces you to specify all attributes so I went for a cleaner, programmatic solution.  

First, we must specify the a redirector for the query created using the EntityManager:

Query query = em.createQuery("getOrdersByCustomerLastName").setParameter("lastName", "Smith");
JpaHelper.getDatabaseQuery(query).setRedirector(new AggressiveCacheQueryRedirector());

EclipseLink's  JpaHelper.getDatabaseQuery()helper method provides access to the EclipseLink-specificDatabaseQuery object which contains thesetRedirector() method.  TheAggressiveCacheQueryRedirector class must implement the QueryRedirector interface.  Here's the complete implementation of the redirector:

 

public class AggressiveCacheQueryRedirector implements QueryRedirector {

    /**
     * Returns a cached result if applicable, otherwise invokes query
     * @param query query to execute
     * @param arguments arguments
     * @param session session
     * @return cached result or query results
     */
    public Object invokeQuery(DatabaseQuery query, Record arguments, Session session) {

        // In order to avoid loop, stop redirecting once execution finishes
        query.setDoNotRedirect(true);

        // Caching read-all queries
        if (query.isReadAllQuery()) {

            // Find or create the cache based on class descriptor
            EhCacheWrapper cacheWrapper = EhCacheWrapper.getInstance();
            Cache cache = cacheWrapper.findOrCreateCache(query.getDescriptor());

            // Create a key for the cache and query it
            CacheKey key = new CacheKey(query.getName(), arguments.values().toArray());
            Element element;

            // Return cached result
            if ((element = cache.get(key)) != null) {
                return element.getValue();

            // Execute query and store result in cache
            } else {
                Object object = query.execute((AbstractSession)session, (AbstractRecord) arguments);
                if (object != null) {
                    cache.put(new Element(key, object));
                }
                return object;
            }
         // Execute query as-is, no caching is done
        } else {
            return query.execute((AbstractSession)session, (AbstractRecord) arguments);
        }
    }
}

It's fairly simple, all it's doing is checking EhCache before deciding whether to execute the query. The query name and argument values are the keys of the cache, with the values being the results returned by the query upon execution. TheEhCacheWrapper class is just that, a wrapper for EhCache specific functions. A plain implementation is given below. Of note is the @AggressiveCache annotation which I created to denote entities that need to be cached using EhCache. It has two attributes, the cache duration in seconds and the size of the cache, corresponding to the setExpiryInSeconds andsetMaxElementsInMemory of the EhCache'sCache object.

public class EhCacheWrapper {

    private static EhCacheWrapper ehCacheWrapper;

    /**
     * Static create method
     * @return The singleton instance of EhCacheWrapper
     */
    public static EhCacheWrapper getInstance() {
        if (ehCacheWrapper == null) {
            ehCacheWrapper = new EhCacheWrapper();
        }
        return ehCacheWrapper;
    }


    /** CacheManager singleton instance **/
    private CacheManager singletonManager;

    /**
     * Private constructor which initializes CacheManager
     */
    private EhCacheWrapper() {
        try {
            singletonManager = new CacheManager(new ClassPathResource("ehcache.xml").getURL());
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }


    /**
     * Returns a cache if one exists, otherwise creates one
     * @param descriptor EclipseLink class descriptor
     * @return A newly created on pre-existing Cache
     */
    public Cache findOrCreateCache(ClassDescriptor descriptor) {
        String cacheName = descriptor.getJavaClass().getSimpleName();
        Cache cache = singletonManager.cacheExists(cacheName) ? singletonManager.getCache(cacheName) : null;
        if (cache == null) {
            Cache newCache = createCache(descriptor);
            singletonManager.addCache(newCache);
            return newCache;
        } else {
            return cache;
        }
    }

    /**
     * Creates a cache either by calling createCacheFromCacheAnnotation() or createDefaultCache()
     * @param descriptor EclipseLink class descriptor
     * @return A newly created Cache
     */
    @SuppressWarnings("unchecked")
    private Cache createCache(ClassDescriptor descriptor) {
        AggressiveCache annotation = (AggressiveCache) descriptor.getJavaClass().getAnnotation(AggressiveCache.class);
        CacheConfiguration config = new CacheConfiguration();
        config.setStatistics(true);
        config.setMaxElementsInMemory(annotation.size());
        config.setTimeToLiveSeconds(annotation.expiryInSeconds());
        config.setEternal(false);
        config.overflowToDisk(false);
        config.setName(descriptor.getJavaClass().getSimpleName());
        return new Cache(config);
    }
}

Here's the @AggressiveCache annotation.

public @interface AggressiveCache {

    /**
     * Corresponds to EhCache's Cache.setMaxElementsInMemory
     * @return EhCache's Cache.setMaxElementsInMemory
     */
    int size() default 1000;

    /**
     * Corresponds to EhCache's Cache.setTimeToLiveSeconds
     * @return EhCache's Cache.setTimeToLiveSeconds
     */
    int expiryInSeconds() default 60000;

}

For completeness, here's the CacheKey class which stores the query name and the argument values.

public class CacheKey {

    private String queryName;
    private Object[] arguments;

    /**
     * Store the query name and its argument as the key
     * @param queryName Name of query
     * @param arguments Arguments of query
     */
    public CacheKey(String queryName, Object[] arguments) {
        this.queryName = queryName;
        this.arguments = arguments;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        CacheKey cacheKey = (CacheKey) o;

        if (!Arrays.equals(arguments, cacheKey.arguments)) return false;
        if (queryName != null ? !queryName.equals(cacheKey.queryName) : cacheKey.queryName != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = queryName != null ? queryName.hashCode() : 0;
        result = 31 * result + (arguments != null ? Arrays.hashCode(arguments) : 0);
        return result;
    }
}
zarar

Are JSPs dead? Blog

Posted by zarar Apr 18, 2007
That

Sure there was fluff, fluff is everywhere and TSS Java Symposium was no different. But in the end there were more code examples than SOA hand-waving and even when the so-called SOA gurus went about trying to sell you stuff, they usually backed it up (or at least tried to) with some kind of a demo w ]]>

The people I work for have rewarded my countless hours of hard labor by approving my application to go to TheServerSide.com's Java Symposium. This comes a year after I attended JavaOne in San Francisco. So why did I choose TSS Java Symposium over Java One? I'm sure you give a rats ass about my opinion but here it comes anyways. JavaOne is too big: It's f***ing huge! There's like 10,000 people there and although you might argue that the size of people attending is proportional to the quality of the presentations, you're wrong. I did attend a couple nice sessions last year but there were more than a few crappy ones (Spring Web Flow, Composite Applications etc.) which tells me (keeping proportions in mind) that there are many crappy ones. The rooms are huge which takes away from the learning environment that it should be; it feels like a first year chemistry class rather than a conference where you're supposed to quickly pick up stuff while having some fun at the same time. The Commercialism:Everyone's trying to sell you something and as soon as you tell them you don't have any purchasing power in your company, they throw sharp pointed objects at you. I talked to the guy from Terracotta last year for about 15 minutes asking him all kinds of questions and at the end he asked me what I did at my company and when I said I was a measly developer, he gave me one of those I-can't-believe-I-wasted-20-f***ing-minutes-on-this-guy look.Bad Food: Just horrible and awful. I was scared to touch it, let alone eat it. But when I finally mustered up the courage to eat it, I regretted it after two bites. I threw the sandwich away and gave my warm soda to what appeared to be a homeless developer.Repetition: There were around four different sessions on Java Persistence API which covered the same subject matter. I made the mistake of attending two of them only to realize they're talking about the exact same thing, they just labeled the sessions differently just so everybody on the expert group had their crack at impressing the bored audience how they copied Hibernate. Bad Party: Any party where the ratio of men to women is 6:1 is never going to be good but you can make up for it by actually providing accessible food and drink. When you shove 10,000 people in one big room and setup 4 stations where you can get drinks and food from, your appetite will force you to exit the premises and the lack of women will only motivate you to do so quickly. I scampered off to the Marriot nearby and admired San Francisco from towering heights. Also, chugging T-Shirts out of a cannon is not entertainment. Long lines at sessions: Again, the size issue. The Gestapo regime that is the event staff forces you to lineup before every session and swipe your conference card. The lineups are long and painful and if you're in the line, you'll often here more than a few people muttering "I don't believe this shit". Don't believe me? Ask anyone who attended last year. Spam after you come back: Here's a tip to anyone attending this year, give a fake phone and email address in your JavaOne registration form. You'll thank me as you're laughing at your friends for having to deal with daily spam and intruding phone calls imploring you to buy some product from Quest Software and pay for training courses from Sun. These people have a knack of bothering you during lunch hour which is a double whammy. Every moment you're in the pavilion, somebody's begging to swipe your conference card like they were get paid by the swipe. They probably are. No Networking Opportunities: Aside from the BOF's which you may or may not be interested in, there are really no parties or events that will allow you to network with other people. Given how most IT folks are socially inward and scared of light, the task of networking at JavaOne is a little tough. The best time I had last year was at the Geronimo party, although I've never even used Geronimo, I sure enjoyed the free food and drinks provided by IBM etc. Side note: Again, aside from the waitresses, no women at this party either. I actually exchanged a few business cards with some fine folk; more of these events would only help JavaOne. Now I've never been to TSS Java Symposium before but from what I can gather from the session descriptions, they seem to cater more to my line of work: enterprise development. I read some horror stories about TSS last year but it's time I take a look and judge for myself. Since I write about other stuff besides just Java, I primarily use a wordpress blog: arsenalist.com. I'll be talking and reviewing the sessions at TSS Java Symposium over there. If anybody cares.  

So you want to migrate to Maven because somebody told you it's the greatest build system around? They're probably right but what they don't tell you is that the road to Maven success is through hundreds of land mines, open JIRA's, mailing list lies and enough internal bleeding to make you wish you had stayed with good ol' Ant even though the build file had reached 4000 lines.

Don't take my word for it, just ask anybody and everybody who's ever converted a project larger than 30 source files from Ant to Maven. God forbid if you're dealing with XDoclet and generated files, if that is the case, then you'll need enough Valium to last you a mvn deploy and no you can't use -Dmaven.test.skip=true. Let's keep the attempts at humor on the side and talk about what really plagues Maven. You, you right there who

A sick feeling encompasses my soul, a wretched sickness comes over me as I sit there staring at this violation of even the simplest of courtesies. I examine it closely and sure enough, it is there, in clear text mocking me, laughing at me, just as I had typed it - letter for letter, digit for digit. No sense of regard showed on the part of the offender, in this case XDoclet's JIRA.

password-o-meter.jpgSo hard have I worked to come up with a word at least eight characters long, containing a digit, an uppercase letter, a symbol and one that I won't forget and you have to ruin it all by sending it to me in clear text over email? Especially even when the password-o-meter told me I had chosen a great password. Why, I ask, Why?

The first thing that comes to mind is that this sacred phrase is most likely stored in a cheap MySQL database without being md5'd. This alone is enough to warrant public execution but I'll let this pass as we live in times where acts of such vileness are tolerated. Who has access to my sacred word? Well, in this case it's the JIRA admin. I know he's (she's?) sitting there on those lonely nights just going through the list of the poor souls he has seduced into supplying him with the key to their lives. I've also been naive (stupid?) enough to supply the same password I use as for my email, youtube, home computer and most importantly bank. Yes, it is my fault, I have shown too much faith in common technology courtesy and will be punished for it by the JIRA admin finding out all about my porn stash.

My best judgment on why these sites don't encrypt passwords is because a) they're lazy, b) they don't know how, c) they want the user to have a copy of the password in case they supplied a really made-up one and will forget it instantly. Let

I try not to complain about lack of documentation. So in this blog entry, I'll show how simple it is to pass in arbitrary data from JSP pages to SiteMeshdecorators.

For every piece of information that you want to pass from the JSP page to the SiteMesh decorator, create a&ltcontent> tag. In the following example, I want to pass in two pieces of data, a "page name" and a "site section". So I must declare two content tags in the JSP page:

<content tag="pageName">
    Login Page
</content>
<content tag="siteSection">
    Administration
</content>

Now, all thats left is telling the SiteMesh decorator to access this information, and that is very easily done using the following syntax in the decorator:

<decorator:getProperty property="page.pageName"/>
<decorator:getProperty property="page.siteSection"/>

That's it. Now you can pass in anything you want and aren't restricted to using <decorator:head/>,<decorator:body/> and<decorator:title/>. My problem with these three tags is that I don't want to declare a head, title and body tag for every JSP page since the JSP page is usually the "main" content of the page and shouldn't have to declare such basic HTML elements. IMHO using <content> tags is a cleaner and more powerful solution as you can pass in an unlimited amount of data without using specific tags. However, combining the three tags mentioned with the <content> tag is also a fine strategy.

zarar

Sneaky, sneaky Log4J Blog

Posted by zarar Nov 14, 2005

So I found myself wanting to know if I could print the enclosing method of the current line of code being executed. A quick look at the reflection API didn't yield much. A little reflective thinking later, I came to the conclusion that it's impossible for the reflection API to tell me this since it explores binary files at the class level.

Thats when it struck me that Log4J does exactly what I want to do. It can print out the method and line number of the code for each log.debug(..) call. So of I went digging into Log4J code looking for an answer. I found it.

What Log4J ends up doing is that for each debug(..)call, it creates an instance of Throwable which takes a snapshot of the runtime stack. It then proceeds to parse the stack to yield the current line of code being executed along with class and method information.

Keep in mind that constructing stack traces is a fairly expensive operation. When an Exception is created, the JVM needs to literally pause the processing and so it can get a good glimpse of the entire runtime stack - this includes classes, methods, line numbers etc - starting from Thread.run()all the way till the creation of Throwable. From the runtime's point of view, this is acceptable since it's not designed for great Exception handling but to run as fast as possible.

However, in the logging scenario, the culprit is the call to theThrowable.fillInStackTrace() method in theThrowable constructor which gets invoked on eachdebug(..), info(..) and other logging calls. This is the only way to retrieve the logging information requested via the PatternLayout class. Give Log4J credit since they only create the Throwable instance when the specific PatternLayout is specified; plus they have the decency to warn you about using the l, L and other expensive options.

Although descriptive logging can provide good roll-back mechanisms in production code, the cost incurred by specifying method and line numbers when logging is simply too high. It seems like the pain of typing out the method name when logging is unavoidable.

To some, this might come as old news but to many it'll make them change their log4j.properties.

Although I've been a core developer for a significant part of my career, I do often come in contact with the business-types who drone on about requirements while the eager-to-please IT shop keeps saying yes to even the most unfathomable requests. After all the yes's have been nodded and all the necessary hands shaken, the Project Manager brings a steep pile of 8.5 x 11's to the developers along with a pat on the back and a confidence inspiring wink. Boo Yeah! The project has started. A meeting is held by all parties involved only to be cut short by 12:00 PM and the ensuing two hour lunch break. The developers are left figuring what this stack of paper is trying to say. The front page is pretty clear, "Inventory Management System" it says. On the second page there's something about Java being a requirement. There's also a due date. They start working. Fast forward to five months later. The stack of paper has a lot of writing on it. Lines have been scribbled and crossed out. Along with bits and pieces of scribble, you'll also find pepperoni stains and some fluids which can best be ascribed to coke spills. Lots of code has been written, some of it has even been tested. The programmers are feeling fairly confident, the PM has his ass covered, anything goes wrong, and he  

Filter Blog

By date: