Pre-detach loading: retrieval by navigation not working in if statement

#1

objectdb-2.6.3_04
JDK1.7
VM option at runtime: -javaagent:lib/objectdb.jar
Experimental mode: objectdb.temp.no-detach NOT USED

This problems only seems to occur in my large web application. I know that mini demonstrations of problems are preferred on this forum, and I have a smaller test web application parallel to the large web application, but the problem does not happen in the smaller web application (as I will demonstrate below), so there is no point making that available (yet). I have been unable to find the point of difference. So I will try first just showing selected portions of code from each and see whether ObjectDB support or your readers can figure out what is happening and why.

I am using pre-detach loading after em.find(id), because just relying on JPA-annotations, which is a "one size fits all" approach, does not meet my needs for all situations. I can configure an entity-query @EJB with a specific loader that - after the em.find(id) is performed - executes selected operations (while "visiting" the loaded managed entity) to load and fetch desired values.

In addition to the persistent fields my entities have transient computation methods, and calling these during the pre-detach loading should (and usually does) load everything needed to compute a transient expert system value, which should then be consistently re-computated once detached and used in a JSF web interface (not further shown here).

I won't give too many details about the entity classes, I will demonstrate the problem I am experiencing first, but you need to know that below:

- A LightingZone is a subclass of entity Block

- A Block has a property 'present' which is a "deep" Boolean value wrapper BooleanValue entity. This is @OneToOne with explicit LAZY fetch.

- A BooleanValue entity wraps a simple Boolean property 'value'.

- A LightingZone has a property 'v_NLA' which is a "deep" Float value wrapper FloatQuantity entity. This is @OneToOne with explicit LAZY fetch.

- A FloatQuantity entity wraps a simple Float property 'value'.

- (Also, the getL_LightingZone() below is a a generic List wrapping entity, with the list  of LightingZone accessed via getL_LightingZone().getEls(). This plays no role  in the problem encountered.)

The following from my real web fails to pre-detach load, it is failing to load the test within the indicated if statement:

@Transient
public Float getNLA_m2_LightingZone() {

Float sum = 0f;
if (getL_LightingZone() != null && getL_LightingZone().getEls() != null) {
    for (LightingZone lz : getL_LightingZone().getEls()) {
        if (lz.getPresent().getValue() != null && lz.getPresent().getValue()) { //FAILS TO LOAD getPresent().getValue()

           //THIS IS NEVER REACHED, ALTHOUGH IN FACT lz.present.value IS true IN THE DATABASE
          //lz.getPresent().getValue has NOT loaded ok for the test.

            Float area = lz.getV_NLA().getValue();
            if (area != null) {
                sum += area;
            } else {
                return null;//POLICY
            }
        }
    }
    return sum;
} else {
    return null;
}
}

But this works, storing the test in a temporary variable:

    test = lz.getPresent().getValue() != null && lz.getPresent().getValue();
    if (test) {

        // TEST NOW PASSES FINE: lz.getPresent().getValue has INDEED loaded ok for the test.
        Float area = lz.getV_NLA().getValue();

There are some other things that also work if used before the if (lz.getPresent().getValue() != null && lz.getPresent().getValue()) test:

- Logging the value of lz.getPresent().getValue() (or sending it to System.out).

- [EDIT: Otherwise making some contrived usage of lz.getPresent().getValue() - before the if statement - that the compiler will not remove.]

This does not work (is not enough) before the if statemetn test:

- Just calling lz.getPresent().getValue() somewhere, but not using the result somehow (I guess the compiler just removes it, for the loading to be triggered it has to be somehow stored in a variable that is eventually used OR just used for logging or output) [EDIT: confirmed using javap -c].

- "Touching" the id with lz.getPresent().getId() before the test. [EDIT: See results of text in next post below.]

I have very carefully (not shown here) also investigated the ObjectDB load states using Persistence.persistenceUtil() before and after that problematic if statement, and it is consistent with what is given above. The 'present.value' and 'v_NLA.value' are null and NOT loaded before the problematic if statement, and 'present.value' is loaded (and true) afterwards, in those cases where it passes the if statement at all (because, for example, the temporary 'test' holder is used).

I tried to isolate it using a simpler test web app with exactly the same pre-detach loading strategy, but could not reproduce the problem. The following works WITHOUT storing the test in a temporary variable:

@Transient
    public Float getComputed() {
        Float val = 0f;
        if (getL_InnerBlock() != null && getL_InnerBlock().getEls() != null) {
            for (InnerBlock ib : getL_InnerBlock().getEls()) {

               //ObjectDB PersistenceUtil claims neither ib.present.value nor ib.present.id are loaded.

                if (ib.getPresent().getValue() != null && ib.getPresent().getValue()) {
                    //ObjectDB PersistenceUtil claims present.value now loaded.           
                    if (f == null) {
                       return null;
                   } else {
                       val += f;
                   }
               }
           }
           return val;
       } else {
           return null;
       }
   }

This test version does not suffer from the same problem, it runs fine without any "load touching" tricks, although the relevant if statement is apparently identical.

It has taken me ages to finally find/identify this problem, as I did not imagine that performing the load access inside an if statement could make a difference (and after all, this did not cause problems in the mini test app). I have tried in vain to find any point of difference between the real web app and the mini test app, I am now truly bamboozled (hence this detailed forum posting).

For the brave, be warned that for this type of problem using a debugger is not the answer (at least, walking through with NetBeans debugger did not help me, as every time you inspect a variable it triggers loading of it, preventing discovery of the real problem.) Similarly, using debug logging can also trigger loading. It's the Schrödinger's cat of JPA.

Darren Kelly, Webel IT Australia

#2

I checked some more things.

This is enough to "provoke" loading of the Boolean lz.present.value before the if statement:

                Boolean presentValue = lz.getPresent().getValue();
                if (presentValue != null) {//Known to in fact be 'true' in database
                        //Perform some contrived usage here ..
                 }
                if (lz.getPresent().getValue() != null && lz.getPresent().getValue()) {
                    //OK REACHED: AND KNOWN TO BE 'true' IN DATABASE

But this (just accessing the lz.present.id) is not:

               Long idPresent = lz.getPresent().getId();//NOT ENOUGH
               if (idPresent < -100L) return null;//PERFORM CONTRIVED USAGE SO COMPILER DOES NOT REMOVE                   
                if (lz.getPresent().getValue() != null && lz.getPresent().getValue()) {
                    //FAILS: NOT REACHED ALTHOUGH KNOWN TO BE 'true' IN DATABASE

The contrived usage is required, otherwise the compiler simply ignores/removes it, which one can confirm with javap -c bytecode inspection.

The @OneToOne entity variable 'present' of a Block entity is a BooleanValue with:

    public BooleanValue() {
    }

    private Boolean value;
    public Boolean getValue() {
        return value;
    }
    public void setValue(Boolean value) {
        this.value = value;
    }

(I won't go here into details about why such value wrappers are being used, but we have very good reasons, including being able to easily reference the "deep" value in an expert system.)

#3
#4

It could be some sort of JVM optimization that went wrong. See:

Try disabling JVM optimizations and using different JVM versions and settings.

Why are using an entity class as a boolean wrapper rather than embeddable class, which could be more efficient (and as a bonus will also eliminate this problem, since no extra loading is required)?

ObjectDB Support
#5

Thanks for prompt reply. I also considered that it might be some kind of JVM optimization, but I can't see anything wrong in the byte code. I will examine your links.

Aside: you asked:

> Why are using an entity class as a boolean wrapper rather than embeddable class,
> which could be more efficient (and as a bonus will also eliminate this problem, since no extra loading is required)?

We have some specific Expert System strategy reasons for using a first-class BooleanValue wrapper entity, including:

- In the real system a BooleanValue wrapper is ultimately an Element that can be referenced by ID, and meets a uniform and powerful IElement interface.

- The BooleanValue is doing a heap more than is shown here, including holding some complex references to other parts of the expert system, having an author history, even the ability to carry documents attachments attesting to the value. It is a very important Boolean value that is being wrapped, with a lot of expert system metadata.

If it were just a case of dealing with a single boolean value it would not make sense to do it this way; but the wrapped boolean value is not the whole story.

 

#6

By JVM optimizations I meant JIT Compiler optimizations and not Java compiler optimization, i.e. the byte code indeed looks fine, but it is not what is really executed, because of aggressive JIT Compiler optimizations.

ObjectDB Support
#7

Running with the JIT disabled using these made no difference:

-Xint  -Djava.compiler=NONE
#8

OK. It could still be an optimization that is used by the JVM interpreter as well.

Have you tried build time enhancement instead of using -javaagent?

Please post relevant byte code after enhancement.

ObjectDB Support
#9

> Have you tried build time enhancement instead of using -javaagent?

I don't usually, because I still have not figured out how to get the compile-on-save feature of NetBeans to "play nice" when using per-file ObjectDB enhancement (as described at this forum posting), but I should be able to get it to work on a complete clean and re-build of the entire project.

It is late here now (in Australia), I might not report back on this until tomorrow morning our time.

Thanks for your continued input.

#10

Support asked:

> Have you tried build time enhancement instead of using -javaagent?

 

I said:

> ... I should be able to get it to work on a complete clean and re-build of the entire project.

 

You beauty, it worked !

Have not tested the consequences for every other part of this large web app yet, but clearly the way ahead is for me to solve the NetBeans "compile-on-save" + ObjectDB per-file enhancement matter, and ignore run-time enhancement for the rest of my life (which is now a few days shorter thanks to time spent on this tricky problem).

 

#11

BTW: I still don't understand why the results would be different between the mini test web app and the large real web app.

#12

Glad to hear that it works. The cause it still unclear. There are no similar previous reports regarding using ObjectDB javaagent enhancer.

Note that the main difference between build time enhancement and on the fly javaagent enhancement is that the later only enhances persistable classes. This should be sufficient, unless you access a persistent field from a non persistable class directly (not using a method) and then that class is considered as persistence aware and requires enhancement (on build time, since the javaagent enhancer only enhances persistable classes).

 

ObjectDB Support
#13

> the main difference between build time enhancement and on the fly javaagent enhancement is that the later only enhances persistable classes

My project's build.xml file Ant script only applies the enhancer to classes inside dedicated entity sub-packages (using <arg line= ..>, and reliably echos when it has enhanced an entity class.

#14

So it is still a mystery since both types of enhancers share exactly the same code.

ObjectDB Support
#15

> So it is still a mystery since both types of enhancers share exactly the same code.

I would rate it as one of the most "mysterious" problems I've ever encountered in my IT career. But assuming that there are no actual Ghosts in the Machine, there must be a point of difference. I checked again and again, the reports about the if statement test failing when both default run-time enhancement and explicit javaagent run-time enhancement (not compile-time) are correct and reproducible.

I am using the JAD decompiler to investigate and compare.

If I disable NetBeans compile-on-save (which seems to use the internal compiler to write .sig files, rather than .class files, to the NetBeans cache, and side-steps any build.xml commands) I can easily apply the ObjectDB enhancer on a per-file basis; I just have to explicitly run/hit Compile for any changed file.

Using JAD on the class files under my build/web/WEB-INF/classes/ I can easily decompile specific class files and see whether the ObjectDB enhancement has taken place. For example:

public class OuterBlock extends Block
    implements TrackableUserType
{

// contains _odb tracking code

public Float getComputed()
    {
        if(getL_InnerBlock() != null && getL_InnerBlock().getEls() != null)
        {
            Iterator i$ = getL_InnerBlock().getEls().iterator();
            do
            {
                if(!i$.hasNext())
                    break;
                InnerBlock ib = (InnerBlock)i$.next();

                if(ib.getPresent().getValue() != null && ib.getPresent().getValue().booleanValue())
                {
                   Float f = ib.getFloatValue().getValue();
                   if(f == null)
                   {
                      return null;
                   }
                   val = Float.valueOf(val.floatValue() + f.floatValue());

The Glassfish4.1 server is set to run over the project in-place at ./build/web/

If I don't use compile-time enhancement - and I do use say the explicit VM option -javaagent:lib/objectdb.jar - I can see that enhancement is taking place, because using reflection on loaded, detached entity objects I can see the hidden odb methods.

But if I apply JAD to any entity classes under ./build/web/WEB-INF/classes after the application has started running, I can't see any enhancement in those class files (which I had assumed are the ones that Glassfish sees "live", but apparently not). [EDIT: they are 'loaded into the JVM' and enhanced into memory.]

[EDIT: question removed, already answered by docs quoted below here]

 

#16

Because of past experiences (mentioned in this older forum posting) I had in fact asked myself (without testing) whether the difference could be the manner of enhancement (run-time javaagent vs compile time), but I did not initially perform the obvious build-and-clean-with enhancement test (until you encouraged it here) because:

1. The mini test app was using javaagent enhancement yet did not exhibit the problem.

2. Support had reassured me that it should not make a difference under forum postings like that older one: Post-compilation enhancement vs Automatic Run-time enhancement, where support wrote:

> There shouldn't be a difference between the two enhancement methods. Both are expected to apply exactly the same code modifications.

Based on my experience, this is clearly not true in an operational environment. Something, somewhere, is different.

#17

Support wrote here:

Automatic on the fly runtime enhancement is applied only to entity classes, where post compilation enhancement may also enhance non entity classes. This may be relevant if you access persistent fields from non entity classes.

As far as I know I do not ever 'access persistent fields from non entity classes', I always use explicit accessors.

I wrote:

> My project's build.xml file Ant script only applies the enhancer to classes inside dedicated entity sub-packages
> (using <arg line= ..>, and reliably echos when it has enhanced an entity class.

As a result of this strategy it also hits:

- Some Enum and Interface types that are (for better or worse) in those same "entity" sub-packages.

Q1: Could that make a difference ?

If so, in order to perform a truly scientifically clean comparison, I might be able to fine-tune my Ant script to detect @Entity and only apply the enhancement to those files, to mimic the behaviour of the javaagent enhancer.

[EDIT: the mini test web app, which does not exhibit the problem, does not have any Interfaces implemented by entities, could this make a difference ?]

#18

I observed above (naively):

if I apply JAD to any entity classes under ./build/web/WEB-INF/classes after the application has started running, I can't see any enhancement in those class files (which I had assumed are the ones that Glassfish sees "live", but apparently not).

ObjectDB explains in enhancer docs here:

Load Time (Java Agent) Enhancement

Instead of enhancing classes during build, classes can be enhanced when they are loaded into the JVM [my underline] by running the application with objectdb.jar as a Java agent.
...
If the JVM is run with ObjectDB Enhancer as a Java Agent, every loaded class is checked and automatically enhanced in memory (if applicable).

Q: Is there any human-friendly way to inspect the classes exactly as they are once loaded into the JVM (in memory) ?

#19

I asked:

As a result of this [build.xml enhancement task] strategy it also hits:

- Some Enum and Interface types that are (for better or worse) in those same "entity" sub-packages.

Q1: Could that make a difference ?

I think the answer is "no", I checked using JAD decompiler and the enhancer does not seem to make any difference to Enum and Interface class files.

#20

The main activity of the Enhancer is to encapsulate persistent fields by methods that first report to ObjectDB and then get or set the persistent field value. If you use property based access then encapsulation is achieved by putting the new encapsulating code in the original get/set methods and moving the original get/set code to new methods that are called by the encapsulating methods after reporting to ObjectDB.

Usually interfaces and enums do not need enhancement and your observations at #19 are expected.

Q: Is there any human-friendly way to inspect the classes exactly as they are once loaded into the JVM (in memory) ?

There is an undocumented internal ObjectDB system property that you can set (on your web application) in order to write enhanced classes  to files:

-Dobjectdb.code.output=>
ObjectDB Support
#21

Thanks for your continued feedback on this issue.

Support wrote:

There is an undocumented internal ObjectDB system property that you can set (on your web application) in order to write enhanced classes  to files:

-Dobjectdb.code.output=<path-on-your-disk>

I tried this as a VM option (along with -javaagent:lib/objectdb.jar) in the NetBeans8.1beta project run config for deployment to Glassfish4.1:

I tried both relative and absolute paths without success:

-Dobjectdb.code.output=./tmp/odb_enhance_rt

-Dobjectdb.code.output=/Users/.../tmp/odb_enhance_rt

I also tried into the GlassFish domains folder (with the same access perms as other domain folders):

-Dobjectdb.code.output=/Applications/NetBeans-8.1beta/glassfish-4.1/glassfish/domains/domain1/tmp/odb_enhance_rt

I got no output in any case [EDIT: I even used UNIX 'find' to very carefully check in each case, and I tried both with initially existing and non-existing folders of those names.]

The run-time enhancement definitely took place (I can see the odbHidden accessor methods in a reflective JSF view).

The issue I reported (otherwise addressed by post-compile Ant task enhancement) occurred again as originally reported.

#22

Sorry, the undocumented system property of #20 was used internally for other purposes (enhancing missing user classes that are built synthetically by the Server and the Explorer).

You should be able to use it now with build 2.6.6_05.

In addition, instead of a system property you can now set the output path in the configuration (objectdb.conf that is available to the the objectdb.jar file that is used as a java agent):

    <entities>
        <enhancement agent="false" reflection="ignore" output="C:\temp\enhanced" />

 

ObjectDB Support

Reply