That's Groovy, Man...

Add the Groovy Scripts power tool to your toolbox today!

Introduction

As many folks know, I'm known for telling people not to look inside the Liferay database itself. It's not always clear what is what in the DB and Liferay has a lot of code around retrieving and updating data that you likely will not get completely right if you were to update the DB directly.

I typically end with "Always, always use the Liferay API for your data needs..."

And, if you're writing code, it is easy to follow this recommendation.

But what if you're a system admin or you're trying to diagnose a problem in an environment that you can't just deploy new code to, how can you follow this recommendation?

Via the Scripting control panel that is part of System Administration, that's how...

What is Groovy?

If you have no background with Groovy, let me just introduce it a bit. Groovy is a scripting language, but it is closely tied to Java. In fact, most Java classes could be copied into a Groovy script and they're pretty much ready to run, the syntax is so connected.

But Groovy is also more than just Java and has some weak-typing support similar to what JavaScript offers. It has some additional language syntax, unique to Groovy and not part of Java.

Groovy is a world practically unto itself. I'm nowhere near a Groovy expert, certainly, I just use it to support my day job so my Groovy knowledge is quite narrow. Whenever I find myself trying to figure out a certain piece of Groovy syntax, I typically end up on the Groovy homepage at https://groovy-lang.org/

The important part for me is that a) it is very close to Java in many ways so it is easy for me to switch between them, b) it is interpreted so I don't have to write and deploy code in the server, and c) I can access pretty much the entire Liferay API to do things quickly...

Examples

So one thing I often find myself needing to do is finding some subset of entities and outputting what I find or doing something with the entity (changing it or evaluating it or ...).

While the complete scripts themselves are probably not very reusable, the techniques I use do tend to be reusable in one form or another. So I want to provide some examples that highlight some of things I've done so that you might be able to leverage them in your own scripts.

Note that what I'm presenting here is not meant to be the only way to do something. There may be better examples or better ways to accomplish the same things I'm doing here; hopefully if folks have a better way they might add them in the comments below so we can all benefit...

Data Retrieval

If anyone asks about any of the static util classes that Liferay has, I'm the first one to step up and say that no one should be using the static utils anymore, we should all be @Reference injecting our services and leave those static util classes in the dust.

But then I clarify, saying that, well, they can be used in cases where we don't have a component and therefore can't @Reference something in. Two examples I often use to support this case, the first is the ServiceBuilder entity model classes; they're just instantiated, they don't have their own component context.

The second example is this one, Groovy Scripts. It is just still so easy to write a line of Groovy like

User user = UserLocalServiceUtil.getUserByEmailAddress(
    PortalUtil.getDefaultCompanyId(), "dnebing@example.com");

So yeah, for your Groovy scripts, feel free to dust off those old legacy static util classes and put them to work. This is one case where it is absolutely appropriate to use them to get to the data that you need.

Searching for Entities

So one thing I often find myself doing is having to find and iterate over a bunch of entities, often needing specific queries to match on, not simple finder-based retrievals. Since I'm also a big fan of Dynamic Queries (https://liferay.dev/en/b/visiting-dynamicquery and https://liferay.dev/en/b/revisiting-dynamicquery), I'll often use this in my Groovy scripts to get the values I need, such as finding all Users with an example.com email address:

DynamicQuery dq = UserLocalServiceUtil.dynamicQuery();

dq.add(RestrictionsFactoryUtil.like("emailAddress", "%@example.com"));

List<User> users = UserLocalServiceUtil.dynamicQuery(dq);

Batch Updates

Some problems require some bulk entity modifications in order to solve them. I'm not going to go to the database to change them there, no I know that I have to use the APIs to ensure that my index gets updated, etc.

So in order to do batch updates, I fall back on another old favorite of mine, the Actionable Dynamic Query (https://liferay.dev/en/b/visiting-dynamicquery#actionableDynamicQuery). Using the ADQ you can batch update your entities while still sticking with the Liferay API to do it correctly.

Let's say we needed to change all email addresses from @example.com domains over to @testing.com domains. We can accomplish this with an easy ADQ implementation:

ActionableDynamicQuery actionableDynamicQuery = 
    UserLocalServiceUtil.getActionableDynamicQuery();

actionableDynamicQuery.setAddCriteriaMethod(
    new ActionableDynamicQuery.AddCriteriaMethod() {
        @Override
        public void addCriteria(DynamicQuery dynamicQuery) {
            dynamicQuery.add(
                RestrictionsFactoryUtil.like("emailAddress", "%@example.com"));
        }
});

actionableDynamicQuery.setPerformActionMethod(
    new ActionableDynamicQuery.PerformActionMethod<User>() {

        @Override
        public void performAction(User user) {
            String emailAddress = user.getEmailAddress();
            int pos = emailAddress.lastIndexOf('@');
            String prefix = emailAddress.substring(0, pos);
	    
            user.setEmailAddress(prefix + "@testing.com");
	    
            UserLocalServiceUtil.updateUser(user);
        }
});

actionableDynamicQuery.performActions();

The great part about this is that I really split my concerns. The setAddCriteriaMethod() is responsible for adding the criteria to select the entities that I need to operate on. The completely separate step, the setPerformActionMethod() is responsible for doing something with the matched user. I don't have to worry about if the system has 5 users with the @example.com domain or whether the system has 5 million users, this code will handle it correctly every time.

In your perform action method, you don't have to limit yourself to the current entity your processing. Say you were trying to create a map of email addresses to blogs count. This code gets you most of the way because you're querying for the users so you'll have the email address for each user, but in the perform action method you could count for all blogs they've authored using the BlogEntryLocalServiceUtil, and perhaps populate the count you get back into a map with the email address as the key and the count as the value.

Locating Services

Not every Liferay service has a static util class for it, and some services have multiple implementations that are differentiated by a property value, so you might one a particular one out of many possible options.

There's two ways you can try it - a Registry lookup and a ServiceTracker.

The Registry lookup is pretty easy to understand:

Registry registry = RegistryUtil.getRegistry()
GroupLocalService groupLocalService = 
    registry.getServices(GroupLocalService.class, null)[0];

The syntax here may look a little odd, but it will get the job done.

The ServiceTracker (or ServiceTrackerList or ServiceTrackerMap if you'd like) can also get the job done, but they have a particular thing you have to pay attention to that is different from regular Java usage:

Bundle bundle = FrameworkUtil.getBundle(
    com.liferay.portal.scripting.groovy.internal.GroovyExecutor.class);
ServiceTracker<GroupLocalService,GroupLocalService> groupServiceTracker = 
    new ServiceTracker(bundle.getBundleContext(), GroupLocalService.class, null);

groupServiceTracker.open();

GroupLocalService groupLocalService = groupServiceTracker.getService();

// use groupLocalService here...

groupServiceTracker.close();

See how we're using the GroovyExecutor class when we're getting the Bundle? The way the Groovy script is processed, we need to use the class running the script, the GroovyExecutor, as the class to find the bundle for.

Here I've used a regular ServiceTracker, but you can also leverage the ServiceTrackerList and ServiceTrackerMap (https://liferay.dev/en/b/service-trackers) when they better suit your needs.

ServiceContext, Current HTTP Request/Response, Etc.

So sometimes you just need one or more of these things, but how the heck can you get them?

The ServiceContextThreadLocal is your friend for this case. This gives up a fairly complete ServiceContext instance and, once you have this, you pretty much have access to whatever you need. Want the current ThemeDisplay? No problem, the ServiceContext getRequest().getAttribute(WebKeys.THEME_DISPLAY); hands it over. There's lots of other goodies in there that you can turn to in a pinch.

import com.liferay.portal.kernel.service.*;

ServiceContext ctx = ServiceContextThreadLocal.getServiceContext();

out.println(ctx.getRequest());
out.println(ctx.getRemoteHost());
out.println(ctx.getRequest().getRequestURI());

I included in this script something I've been leaving out of the other samples: the imports. Just like in Java, your Groovy Scripts must import classes that are necessary for the script to run. Without them, you'll get all kinds of fun Unable to resolve class Groovy errors to figure out. Fortunately it will tell you what you're missing, so it is pretty easy to get them squared away.

Logging

Groovy gives you System.out as as the simple out global variable that you can use to write to the console, but you can use the logger too. I like using slf4j, so sometimes I'll include in my scripts:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger logger = LoggerFactory.getLogger(
    com.liferay.portal.scripting.groovy.internal.GroovyExecutor.class);

logger.info("Groovy script Hello World is running...");

Why would you want to use a logger in this way? Maybe you want a record of the scripts execution and results in the log so you can find them later after you've moved away from the Scripting control panel. Maybe you want the output in the logs because other people are running scripts and you're counting on the logging to keep track of them all. Maybe you don't like the way the Execute button in the Scripting control panel is all the way at the bottom, and if you have a lot of output text you have to scroll forever to get to the Execute button to run it again, and logging instead of using out for the messages means you won't need to scroll.

I don't know, I'm sure you might find a reason and, if you do, you now know it's an option. Oh and you don't have to use the GroovyExecutor class like I did here. You could use another class or you could pass any old string to get a logger.

Conclusion

The Scripting control panel and Groovy Scripts represent power tools for you to keep handy in your toolbox. They offer an ability to execute dynamic code fully leveraging the Liferay API so any verification and validation Liferay does as part of the API call will be in effect for your script.

Although Groovy has a lot of its own special syntax that you can use, my scripts often look like straight-up Java code. Most of the time I work in Java so avoiding special Groovy syntax will help me avoid trying to use Groovy syntax in regular code where I can't use it at all. But please feel free to use it if it feels natural or a better or more concise way to code for a script. You do you!

I hope you can use some of these snippets I've shared.

If you have a library of snippets that you use or other useful tidbits, please drop them in a comment below!

Blogs

The ServiceContextThreadLocal.getServiceContext() trick is really cool, I never used it in Groovy console. I always kept mocking ThemeDisplay and Request objects. Thanks for that!