What if Liferay could return your customized entity from an existing service

Wouldn't that be a hoot? But how do we do that? Simple, we use AOP (Apsect Oriented Programming). Ok, you tried that but couldn't make it work with Liferay. Well, that's what this particular post is all about. I'll show you how in three simple steps.

Ok, so first off your custom entity MUST extend the entity of the service in question.

Here is an example of an entity which extends Layout(Impl, extend the Impl always.. otherwise you will have problems):

package com.ray.portal.model.impl;

import ...

public class VersionLayoutImpl extends LayoutImpl {

	// Copy All these methods from *ModelImpl of the entity you are extending
 	// and adjust as needed for your custom type

	public static Layout toModel(LayoutSoap soapModel) {
		VersionLayoutImpl model = new VersionLayoutImpl();

		model.setPlid(soapModel.getPlid());
		...

		return model;
	}

	public Layout toEscapedModel() {
		if (isEscapedModel()) {
			return (Layout)this;
		}
		else {
			Layout model = new VersionLayoutImpl();

			model.setNew(isNew());
			...

			return model;
		}
	}

	public Object clone() {
		VersionLayoutImpl clone = new VersionLayoutImpl();

		clone.setPlid(getPlid());
		...

		return clone;
	}

	public static VersionLayoutImpl clone(LayoutModelImpl layout) {
		VersionLayoutImpl clone = new VersionLayoutImpl();

		clone.setPlid(layout.getPlid());
		...
		
		return clone;
	}

	public int compareTo(Object obj) {
		...
	}

	public boolean equals(Object obj) {
		...	
	}

}

We also added a static clone method to make it easier to go from the stock entity to the custom one.

Next, you need to replace the returned types from the existing service, so you need some way to inject your types in their place. To do this write a pretty simple org.aopalliance.intercept.MethodInterceptor implementation like so:

package com.ray.portal.service.aop;

import com.liferay.portal.model.impl.LayoutModelImpl;

import java.util.ArrayList;
import java.util.List;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import com.ray.portal.model.impl.VersionLayoutImpl;

public class LayoutLocalServiceInterceptor implements MethodInterceptor {

	public Object invoke(MethodInvocation invocation) throws Throwable {
		Object result = invocation.proceed();

		if (result instanceof LayoutModelImpl) {
			result = wrapLayout(result);
		}
		else if (result instanceof List) {
			result = new WrappedList((List<LayoutModelImpl>)result);
		}

		return result;
	}

	protected VersionLayoutImpl wrapLayout(Object object) {
		if (object instanceof VersionLayoutImpl) {
			return (VersionLayoutImpl)object;
		}

		return VersionLayoutImpl.clone((LayoutModelImpl)object);
	}

	public class WrappedList extends ArrayList<LayoutModelImpl> {

		public WrappedList(List<? extends LayoutModelImpl> list) {
			super(list);
		}

		public VersionLayoutImpl get(int index) {
			return wrapLayout(super.get(index));
		}

	}

}

Pretty simple right? All we need to do now is wire this interceptor into the IOC container and we're all set.

As usual we make a visit to our friend ext-spring.xml and add:

	<aop:config>
		<aop:pointcut id="versionLayoutOperation" expression="bean(com.liferay.portal.service.LayoutLocalService.impl)" />
		<aop:advisor advice-ref="versionLayoutInterceptor" pointcut-ref="versionLayoutOperation" />
	</aop:config>
	<bean id="versionLayoutInterceptor" class="com.ray.portal.service.aop.LayoutLocalServiceInterceptor" />

All done! So, you can add/override methods on your entity and othewise customize away.

Cool eh? Enjoy!

Blogs
Ah, really like it emoticon. Simple and powerful with some help of Spring.
Hey Ray,

You should be able to write a generic Advice for this that takes a customized entity as a dependency. Also, you actually don't need to implement an interface to implement the advice.
Thank you, Ray and Michael!

Love to see how to implement the advice ...
In addition to the approach above, which is still a good approach for any version of the portal before rev 27426, we now have this as a feature in the core. So we've done most of the heavy lifting for you.

I'll blog again about how to use this new feature.
You stated that this was being done so...how would you override an existing model class in a portlet/plugin in Liferay 6.0 (or 5.2)? Any references would be appreciated.
Yeah, what you want to read about is ServiceWrappers:

http://www.liferay.com/community/wiki/-/wiki/Main/Wrapper+Plugins

It's not the most well documented aspect, but there is an example at least in the "test-hook-portlet".

Basically, extend the Wrapper class of the Service in question, and you will also find that you can also extend the entity's Wrapper class and return that.
My goal is to add custom methods to a large number of classes.... any more thoughts that you have would be appreciated.

I could do the following:
1.) Create a service wrapper class as stated in "Wrapper Plugins"
2.) Create a Model class that extends the original model and add a custom method
3.) For each method that returns a set of Models, wrap the model in my custom model. This is more of an Adapter/Proxy pattern.

This is a fairly heavy-maintenance process (if large numbers of model classes need to be extended) since 2 proxy classes (model and service) that just delegate method calls have to be created along with lots of method overrides.

It seems that an easier approach might be to do the following
1.) Use AOP to create the extended models
2.) Use Service Wrappers to extend the service
3.) Each service method returning the model(s) in question should have a slightly different method signature returning the extended class. Each overridden method could then just return the the proxied service call and perform a cast without having to loop over lists of objects (from finders, etc.), wrap them, and return the new list.

What are your thoughts, since my real goal is to add custom methods to large numbers of existing models?
You have ServiceWrappers but well we have ModelWrappers where you can add your custom methods. So your ServiceWrapper can return your ModelWrappers for any method you need them.

When returning lists, we don't wrap all at once, but rather lazily by creating an extended List object which only wraps entries when the list's getters are called. This eliminates iterating over large sets. It doesn't get around having to cast Models to the new Wrapper impl though.
Thanks for the response...one further question...

How do I specify in service.xml (or other mechanism..like spring file) as to what my model wrapper implementation will be for a specific service model class? Or do I need to manually wrap them with my custom model wrapper in my custom service methods? Is there a convention to the Model wrapper that must be followed?

Thanks
so, you need to use a ServiceWrapper to output ModelWrappers, but the model wrappers are generated for you (they are in the service jar). You simply pass the original object in the constructor:

return new UserWrapper(user);

Now to add your customized methods, simply extend the generated ModelWrapper (you have to put that class in your service.jar) and use instances of the extension when wrapping:

return new MyCustomUserWrapper(user);
Hi experts - I developed a simple service and used AOP on a method. If I put my adivce class in the ROOT/WEB-INF/classes/, the functionality works as expected. But if I dont put the classes there, I get a class not found exception. What is the issue here ?
The aop: syntax does not appear to work in the 5.1.2 ext-spring.xml
[...] I think the below post may help. Especially the last few comments by Ray Auge. You could override those methods or add additional methods to your custom model wrapper and return them.... [...] Read More
I would like to use the liferay Document Library Json service. I could use the standard DL service in ROOT, but I don't want to have to pass in security credentials, I'd rather use the standard portlet security (header-based) auth that generally involves forwarding all http communication through the portlet serveResource method. So I'm not sure what the best way there is to get DL - JSON functionality without passing security credentials??...any thoughts????