Custom Finder for Portal Entities in a Plugin

Sometimes you need to write a custom finder for a table that belongs to core. In my case it was JournalArticle.  My client wanted to avoid using the ext-plugin as much as possible so I had to try to figure out a way to do it within a portlet plugin.

I tried to use dynamic queries as a solution but it turned out to be inadequate because I was unable to group by (I wanted to get the latest articles with a certain structureId).

I needed a custom finder but for awhile I thought this was impossible because you needed to load the Impl of a model during the query which is inaccessible since it is part of portal-impl.  But my experimentation with dynamic queries was not in vain because I figured... if dynamic queries can figure out a way to load the impl class I could probably do the same.

So here is the class I wrote.

It consists of two basic methods:


public static Class getImplClass(Class clazz, ClassLoader classLoader) {
    if (!clazz.getName().endsWith("Impl")) {
        String implClassName = clazz.getPackage().getName() + ".impl." + 
            clazz.getSimpleName() + "Impl";

       	clazz = _classMap.get(implClassName);

       	if (clazz == null) {    
            try {
               			if (classLoader == null) {   
                    Thread currentThread = Thread.currentThread();

                    classLoader = currentThread.getContextClassLoader(); 
                }

                clazz = classLoader.loadClass(implClassName); 
               _classMap.put(implClassName, clazz); 
            }
            catch (Exception e) {
                _log.error("Unable find model " + implClassName, e);
            }
        } 
    }
    return clazz;
}
	
public static Session openPortalSession() throws ORMException {
    return sessionFactory.openSession();	
}

private static Log _log = LogFactoryUtil.getLog(CustomFinderHelperUtil.class);

private static Map> _classMap = new HashMap>();
	
private static SessionFactory sessionFactory =
    (SessionFactory)PortalBeanLocatorUtil.locate("liferaySessionFactory");

So basically in my customFinder instead of doing

session = openSession();

I did

session = CustomFinderHelperUtil.openPortalSession();

and instead of doing

q.addEntity("JournalArticle",  JournalArticleImpl.class));

I did

q.addEntity("JournalArticle", CustomFinderHelperUtil.getImplClass(
                JournalArticle.class, true));

(I created a extra method that took a boolean to use the portalClassLoader, you can load it using PortalClassLoaderUtil.getPortalClassLoader())

Hopefully this helps some you guys out.

 

Blogs
May be the SQLQuery(Impl) could be extended so that if there is entity's interface as parameter then, it would find the implementation automatically.

Anyway, good post.
FYI

You are using _classMap in a non threadsafe way

Also by holding on to the impl class references in a map you are effectively preventing the webapp classloader from being garbage collected. (not a problem if you just use it to load classes from the ROOT webapp, since it's not hotdeployable, but it is if you use this same trick do load classes from another standalone portlet) You'll be blowing up your permgen space
Hey jelmer,

Good points. I will update my code so that if the classLoader is not ROOT it won't put it in the map. You should consider filing a jira issue also because this code is a copy of what is from DynamicFactoryUtil

Thanks for your comments guys
Hey Sampsa,

That would make things too easy for you guys then! Haha JK. Good suggestion. It is even more valid because the look up is already being done for dynamic queries. Very little code would have to be written for this improvement.
It is a bit challenging to add a new action with a custom jsp and ensuring that the jsp is wrapped into the theme properly. I have got the solution and it works like a charm. You can find the solution here : http://www.liferayaddict.com/home/-/blogs/new-action-in-hook-in-liferay-6-1
Great! i asked to me the same question "If DynamicQueryFactoryUtil.forClass can, me too. Right?" thanks.
For now i have other issue, i try to do a CustomSQL Query where IS SELECT * customTable INNER JOIN JournalArticle. For now i don't find other solution that modifying portlet-hbm adding the portal table configuration from portal-hbm. Like is suggested in http://www.liferaysavvy.com/2013/02/getting-data-from-multiple-tables-in.html. But if you are interested and you find other solution, that will be great. Regards from Colombia.