-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: Shared Second-Level-Cache and multiple ClassLoaders
PostPosted: Thu Mar 03, 2011 7:47 am 
Newbie

Joined: Wed Mar 02, 2011 2:38 pm
Posts: 2
Hello, everyone!

First of all, I want to apologize for the long description. It is so long to be clear what the problem is.
Recently I have run into the situation, when I need to share second-level-cache between multiple web applications inside the same servlet container.

Original intent was to split monolithic application into several logically independent applications, either by configuration or by build procedure. The task is done. And now I have several web applications that share the same codebase but logically are independent from each other and can be deployed/undeployed on demand.

Next task was enable support for Second-Level-Cache in Hibernate and share it between the applications (because they use the same database and the same entities). And here comes the problem.

Due to the fact that different web applications have different class loaders – sharing is not happening. Equals method in Hibernate’s CacheKey is unable to compare CacheKeys loaded by different class loaders, as a result duplicated entries appear in the cache – one for each application. That makes usage of second-level-cache not so attractive for applications with big amount of data. And that is exact kind of applications which could greatly benefit from using a cache.

I tried to move Hibernate libs to server libraries, so that it will be available from parent class loader. But it doesn’t resolve the problem completely. New issue aroused – Enumerations.

Enums are serializable by their nature, and they are being put to cache as is. So cache contains enums loaded from different class loaders. When Hibernate tries to reconstruct entity from cache, IllegalArgumentException is thrown on attempt to set Enum loaded from different class loader to entity loaded from current class loader.

Moving entity classes to parent class loader could help, but it makes application deployment ridiculously complex. I must take care of separation of entity classes from rest of the application classes and update server library. That also makes distributed deployment impossible.

Finally, I came up with the next approach that resolved the issue for me for the time being. I created aspect that “patches” hibernate cache classes and made them class loader agnostic. I just want to share my experience with the cache, so others could review it, and may be Hibernate team will come up with better solution for this problem.

Here is the aspect code
Code:
@Aspect
public class HibCacheKeyEqualsOverride {

    @Pointcut("execution(public boolean org.hibernate.cache.CacheKey.equals(Object)) && args(other)")
    public void cacheKeyEqualsPoincut(final Object other) {}

    @Around(value = "cacheKeyEqualsPoincut(other) && this(key)", argNames = "pjp,key,other")
    public Object cacheKeyEqualsAdvice(final ProceedingJoinPoint pjp, final org.hibernate.cache.CacheKey key,
                                       final Object other) throws Throwable
    {

        if ( !(other.getClass().getName().equals(CacheKey.class.getName()))) {
            return false;
        }

        if(other instanceof CacheKey) {
            return pjp.proceed();
        }

        final Field keyField = key.getClass().getDeclaredField("type");
        keyField.setAccessible(true);
        final Type keyType = (Type) keyField.get(key);
        keyField.setAccessible(false);

        final Method entRoleMethod = other.getClass().getDeclaredMethod("getEntityOrRoleName");
        final Method keyMethod = other.getClass().getDeclaredMethod("getKey");

        return key.getEntityOrRoleName().equals( entRoleMethod.invoke(other) )
                && keyType.isEqual( key.getKey(), keyMethod.invoke(other), EntityMode.POJO );
    }

    @Pointcut("execution(public Object org.hibernate.cache.entry.UnstructuredCacheEntry.destructure(Object,..)) && args(obj,..)")
    public void destructureUnstructuredPointcut(Object obj) {}

    @Around(value = "destructureUnstructuredPointcut(obj)", argNames = "pjp, obj")
    public Object destructureUnstructuredAdvice(final ProceedingJoinPoint pjp, final Object obj) throws Throwable {


        if("org.hibernate.cache.entry.CacheEntry".equals(obj.getClass().getName()) && !(obj instanceof CacheEntry)) {

            final Class cls = obj.getClass();
            Object o1 = cls.getDeclaredMethod("getSubclass").invoke(obj);
            Object o2 = cls.getDeclaredMethod("areLazyPropertiesUnfetched").invoke(obj);
            Object o3 = cls.getDeclaredMethod("getVersion").invoke(obj);
            Serializable[] o4 = (Serializable[]) cls.getDeclaredMethod("getDisassembledState").invoke(obj);

            Serializable[] localData = new Serializable[o4.length];

            for(int i=0; i < o4.length; i++) {
                localData[i] = localize(o4[i]);
            }

            final Constructor<CacheEntry> constr = CacheEntry.class.
                    getDeclaredConstructor(Serializable[].class, String.class, boolean.class, Object.class);
            constr.setAccessible(true);

            return constr.newInstance(localData, o1, o2, o3);
        }

        return obj;
    }

    @Pointcut(value = "execution(public Object org.hibernate.cache.entry.StructuredCacheEntry.destructure(Object, *.hibernate.*.SessionFactoryImplementor)) && args(obj,sf)", argNames = "obj,sf")
    public void destructureStructuredPointcut(Object obj, org.hibernate.engine.SessionFactoryImplementor sf) {}

    @Around(value = "destructureStructuredPointcut(item, sf)", argNames = "pjp, item, sf")
    public Object destructureStructuredAdvice(final ProceedingJoinPoint pjp, final Object item,
                                              final org.hibernate.engine.SessionFactoryImplementor sf) throws Throwable
    {
        final Map<String, Serializable> map = (Map<String, Serializable>) item;
        final Map<String, Serializable> localMap = new HashMap<String, Serializable>(map.size());

        for(Map.Entry<String,Serializable> entry : map.entrySet()) {
            localMap.put(entry.getKey(), localize(entry.getValue()));
        }

        return pjp.proceed(new Object[]{localMap, sf});
    }

    @Pointcut(value = "execution(public Object org.hibernate.cache.entry.StructuredCollectionCacheEntry.destructure(Object, *.hibernate.*.SessionFactoryImplementor)) && args(obj,sf)", argNames = "obj,sf")
    public void destructureStructuredCommectionPointcut(Object obj, org.hibernate.engine.SessionFactoryImplementor sf) {}

    @Around(value = "destructureStructuredCommectionPointcut(item, sf)", argNames = "pjp, item, sf")
    public Object destructureStructuredCommectionAdvice(final ProceedingJoinPoint pjp, final Object item,
                                              final org.hibernate.engine.SessionFactoryImplementor sf) throws Throwable
    {
        final List<Serializable> list = (List<Serializable>) item;
        final List<Serializable> localList = new ArrayList<Serializable>(list.size());

        for(Serializable entry : list) {
            localList.add(localize(entry));
        }

        return pjp.proceed(new Object[]{localList, sf});
    }

    @Pointcut(value = "execution(public Object org.hibernate.cache.entry.StructuredMapCacheEntry.destructure(Object, *.hibernate.*.SessionFactoryImplementor)) && args(obj,sf)", argNames = "obj,sf")
    public void destructureStructuredMapPointcut(Object obj, org.hibernate.engine.SessionFactoryImplementor sf) {}

    @Around(value = "destructureStructuredMapPointcut(item, sf)", argNames = "pjp, item, sf")
    public Object destructureStructuredMapAdvice(final ProceedingJoinPoint pjp, final Object item,
                                              final org.hibernate.engine.SessionFactoryImplementor sf) throws Throwable
    {
        final Map<Serializable, Serializable> map = (Map<Serializable, Serializable>) item;
        final Map<Serializable, Serializable> localMap = new HashMap<Serializable, Serializable>(map.size());

        for(Map.Entry<Serializable,Serializable> entry : map.entrySet()) {
            localMap.put(localize(entry.getKey()), localize(entry.getValue()));
        }

        return pjp.proceed(new Object[]{localMap, sf});
    }


    private static Serializable localize(final Serializable serializable) throws ClassNotFoundException {
        Serializable localized = serializable;
        if(serializable != null) {
            if(serializable.toString().equals(LazyPropertyInitializer.UNFETCHED_PROPERTY.toString())) {
                localized = LazyPropertyInitializer.UNFETCHED_PROPERTY;

            } else if(serializable.toString().equals(BackrefPropertyAccessor.UNKNOWN.toString())) {
                localized = BackrefPropertyAccessor.UNKNOWN;

            } else if(serializable.getClass().isEnum()) {
                localized = Enum.valueOf((Class<Enum>) Class.forName(serializable.getClass().getName()),
                                         ((Enum) serializable).name());
            }
        }
        return localized;
    }

}

and here is the aop.xml
Code:
<aspectj>
    <aspects>
        <!-- declare aspects to the weaver -->
        <aspect name="HibCacheKeyEqualsOverride"/>
    </aspects>
    <weaver options="-verbose -showWeaveInfo -Xreweavable"> <!--options="-verbose -showWeaveInfo"-->
        <!-- Weave types that are within the org.hibernate.cache.* -->
        <include within="org.hibernate.cache..*"/>
        <!-- by some mystical reasons the aspect itself requires weaving
             otherwise MethodNotFoundException .aspectOf() will be thrown -->
        <include within="HibCacheKeyEqualsOverride"/>
    </weaver>
</aspectj>


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.