Resolving Missing Components

So if you've started developing for Liferay 7 CE / Liferay DXP, I'm sure you've been hit at one point or another with the old "unresolved reference" issue that prevents your bundle from starting.

You would have seen it by now, the Gogo shell where you list the beans and find your bean there stuck in the Installed state. You try starting it and Gogo tells you about the unresolved reference you have and you're stuck going back to your bnd.bnd file to resolve the dependency issue.

This is so common, in fact, that I wrote a blog post to help resolve them: https://web.liferay.com/web/user.26526/blog/-/blogs/osgi-module-dependencies

While this will be your issue more often than not, there's another form of "unsatisfied reference" problem that leads to missing components rather than non-started bundles.

The Case of the Missing Component

You can have a case where your module starts but your component is not available. This sounds kind of strange, right? You've taken the time to resolve all of those 3rd party dependency jars, the direct and transitive ones, and your bean starts cleanly and there are no errors.

But your component is just not available. It seems to defy logic.

So, here's the skinny... Any time your component has an @Reference with default binding, you are basically telling OSGi that your component just has to have the reference injected or else it cannot be used.

That's where this comes from - you basically have an unsatisfied reference to some object that was supposed to be @Reference injected but could not be found; since the reference is missing, your component cannot start and it is therefore not available.

There's actually a bunch of different ways that this scenario can happen:

  • The @Reference refers to an object from another module that was not deployed or has not started (perhaps because of normal unresolved references). This is quite common if you deploy your Service Builder API module but forget to deploy the service module.
  • You have built in circular references (more below).
  • You use a target filter for the @Reference that is too narrow or incorrect, such that suitable candidates cannot be used.

In all of these cases you'll be stuck with a clean component, just one that cannot activate because of unsatisfied references.

Sewing (@Reference) Circles

Reference circles are real pains to resolve but they rise out of your own code. Understanding reference circles is probably best started through an example.

Let's say we are building a school planning system. We focus on two major classes, a ClassroomManager and an InstructorManager. The ClassroomManager has visibility on all classrooms and is aware of the schedule and availability. The InstructorManager has the instructor roll and is aware of their schedule and availability.

It would be quite natural for a ClassroomManager to use an InstructorManager to perhaps find an available instructor to substitute in a class. Likewise it would be natural for an InstructorManager to need a ClassroomManager to try to reschedule a class to another time in an available room.

So you might find yourself creating the following classes:

@Component
public class ClassroomManager {

  @Reference
  private InstructorManager instructorManager;
}

@Component
public class InstructorManager {

  @Reference
  private ClassroomManager classroomManager;
}

If you look at this code, it seems quite logical.  Each class has a need for another component, so it has been @Reference injected. Should be fine, right?

Well actually this code has a problem - there's a circular reference.

When OSGi is processing the ClassroomManager class, it knows that the class cannot activate unless there's an available, activated InstructorManager instance to inject. Which there isn't yet, so this class cannot activate.

When OSGi is processing the InstructorManager class, it knows that the class cannot activate unless there's an available, activated ClassroomManager instance to inject. Which there isn't yet, so this class cannot activate.

But wait, you say, we just did the ClassroomManager, we should be fine! We're stuck, though, because the ClassroomManager could not activate because of the unsatisfied reference.

This is your reference circle - neither component can start because they are circularly dependent upon each other.

Resolving Component Unsatisfied References

Resolution is not going to be the same for every unsatisfied component reference.

If the problem is an undeployed module, resolving is as simple as deploying the missing module.

If the problem is an unstarted module, resolving is a matter of starting the module (perhaps fixing whatever problem that might be preventing it from starting in the first place).

For a reference target filter issue, well those are going to be challenging. You'll have to figure out if the target is not right or too narrow and make appropriate adjustments.

The circular reference resolutions can be resolved by refactoring code - instead of big ClassroomManager and InstructorManager classes, perhaps use a bunch of smaller classes that don't result in similar reference circles.

Another option is to use different ReferenceCardinality, ReferencePolicy and ReferencePolicyOption values (see my blog post on annotations, specifically the section on the @Reference annotation). You could switch both from MANDITORY to OPTIONAL ReferenceCardinalities, DYNAMIC for the ReferencePolicy, ...  The right set is usually mandated by what the code can handle and requires, but the outcome would allow the components to activate without the initial references being satisfied, but once activated the references will be post-injected.

How Do You Fix What You Can't Find?

This, for me, has been kind of a challenge. Of course the answer lies within one of the Gogo shell commands, but I've always found it hard to separate the wheat (the components with unsatisfied references) from the chaff (the full output with all component status details from the bundle).

For me, I've found it easiest to use TripWire CE or TripWire DXP. After going to the TripWire panel in the control panel, click on the Take Snapshot card and you can actually drill into and view all unsatisfied references.  The following screen capture is an actual view I used to resolve an unsatisfied reference issue:

The issue I was looking at was the first unsatisfied reference line for my com.liferay.metrics.health.portal.layouts.LayoutHealthCheck component. It just wouldn't activate and I didn't know why; I knew it wasn't available, it wasn't doing its thing, but the module was successfully started so what could the problem be?

Well the value for the key spells it out - I have two unsatisfied references for two different gauge instances. And you know what? it is totally true. Those two missing references happen to be the 2nd and 3rd lines in the list above, and they in turn had unsatisfied references that needed to be resolved, ...

Conclusion

The point here is that in order to resolve unsatisfied references, you need to be able to identify them. Once you can identify the problem, you can resolve them and move on.

For me, I've found it easiest to use TripWire CE or TripWire DXP to identify the unsatisfied references, it does so quickly, easily, and doesn't require memorizing Gogo shell commands to get it done.

 

0