« Back

The Power of Patience

Technical Blogs February 2, 2018 By David H Nebinger

So, as a developer, I'm like usually whacking my whole runtime environment and starting over.

Why? Well, regardless how much I try to keep it clean, cruft will find its way into the environment. I'm left with users I don't want, content I don't want, pages I'm not using any more, sites created to demo something I'm not demoing any more...

So I'll throw out my data directory, drop my database and create a new one, purge old logs, modules, osgi/state and the work directories, ... Basically I get back to a point where I am starting up a brand new bundle, all shiny and new.

One of the things I often find when I do this, though, is that I'm a lazy developer.

So for awhile I was actually writing code that had dependencies on some environmental aspect. For example, my code would assume that a custom field had been created, roles or organizations were defined, ...

When I would bring up the environment, my code would fail because I had forgotten to create the dependent data.

So I got a little smarter, and I learned about the Liferay Upgrade framework. I now use this framework when writing my modules so I can just deploy my modules and, if the dependent data is missing, it will be created automagically. This works really great and, from a deployment perspective, I can promote my modules to test, QA, and prod knowing that my modules will create whatever they have to have to function properly.

This has been working really well for me, well, until today.

Today I did one of my purges and then fired up the environment and my upgrade process was failing.

I had created an upgrade process that was creating a custom field for the User model object. The code totally works when deploying to a previously-running instance, but I was getting an error during startup after the purge because my code was executing before the portal finished loading all of the default data. So one of the things you need for a custom field is a company id to bind them to, but my upgrade process was running before the first Company record was created.

Not company record, no company ID, and my code was just throwing all kinds of exceptions.

So I was effectively still being lazy. I was assuming that my code always ran at a particular time and that certain things were already available, and I found that when starting up from a clean state my assumptions caused my code to fail.

Basically I wasn't waiting for a good time for my code to execute (well, I didn't think I had to wait).  As a module developer, we can all often assume that if our code works on a hot deploy, well then it will always work. The problem, of course, is that environment startup we really don't have control over when our modules load, when our components start. As far as OSGi is concerned, if the references have been resolved and injected, the component is good to go.

But often times our components may have some sort of dependency that is hard to declare. For me, I was dependent upon the initial Company record being available, and I can't really declare a dependency on that which OSGi would be able to verify. In this forum thread, the code wanted to unload a component that had not been loaded/started when the componen t was activated, so sometimes the right thing happened and sometimes it didn't.

So the key is to identify when you have code that really needs to wait on something, but also to have something identifiable to OSGi to wait on.

What if you can't identify something for OSGi such as the initial Company record? Or the creation of some service in a different module that you don't want to declare as an @Reference in your component?

One trick I like to do to solve this case is to add a final dependency on portal startup. By adding the following to your @Component class, you will typically get the right outcome:

@Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED, unbind = "-")
protected void setModuleServiceLifecycle(ModuleServiceLifecycle moduleServiceLifecycle) { }

That's it. This code basically has OSGi wait to start your component until the portal has been initialized. Note that I'm not doing anything with the injected object; I'm just forcing OSGi to delay the start of my component until it can be injected.

Adding this line to my Upgrade class, I did my purge stuff and started up the portal. After the portal was initialized, the Company record is guaranteed to be available, and my Upgrade had no problem creating the custom field.

Success!

So, keep this in mind if you need to ensure that some component doesn't try to do its thing too early in the startup process.

Oh, and if you just need to enforce a start sequence between your own components? Well, just remember that OSGi will not start a component until its @References can be injected. So if B depends on A, and C depends on B, just add the necessary @Reference guys to B and C. At startup, OSGi will be able to start A (since it has no dependencies), which means it can then start B and finally start C, guaranteeing the startup sequence that you need to have.

No comments yet. Be the first.