MorphiaOrg / morphia

MongoDB object-document mapper in Java based on https://github.com/mongodb/mongo-java-driver
Apache License 2.0
1.64k stars 456 forks source link

2.0.1 guys ..null pointer issues when trying to determine if Reference is not there (ie an optional relationship) #1512

Closed johnda98 closed 3 years ago

johnda98 commented 3 years ago

2.0.1 guys ..null pointer issues when trying to determine if Reference is not there (ie an optional relationship)

how are optional relationships determined ? .. If no link between entities then no say List appears in mongo.. which is ok.. but in the code Entity.getmethod() sends a null pointer.. even if I load the list with a null I cannot pickup the null as Java trips.

sooooo.. in morphia reference.. for optionality we create the List reference anyway but with an objectid say that is a meaningless object.. just so we can determine if the relationship is not there ie for that entity say it is optional

thoughts ?

evanchooly commented 3 years ago

It would help to see code and stack traces.

johnda98 commented 3 years ago

here's the entity

package org.crob;

import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.mapping.experimental.MorphiaReference;
import org.bson.types.ObjectId;
import java.util.List;

@Entity("dataholons")
public class DataHolon {
        @Id
        private ObjectId id;
        private String name;
        private String description;

        private MorphiaReference<List<DataHolon>> references;

        private MorphiaReference<List<DataHolon>> referencedBy;

        private MorphiaReference<List<DataHolonType>>  isaclassificationOf;

        private MorphiaReference<List<Interface>>  interactsWith;

        private MorphiaReference<List<DataHolonProperty>> consistsOf;

        private MorphiaReference<List<Scene>> usedIn;

        DataHolon() { }

        DataHolon(final String name, final String description){
            this.name = name;
            this.description = description;
        }

        public ObjectId getId() {
            return id;
        }

        public String getName(){
            return name;
        }

        public String getDescription(){
            return description;
        }

        // References to/ by - recursive

        public List<DataHolon> getReferences() { return references.get();}

        public List<DataHolon> getReferencedBy() {return referencedBy.get(); }

        // get Data holontype

        public List<DataHolonType> getIsclassificationOf() {
            return isaclassificationOf.get();
        }

        // get uses interfaces(apis)

        public List<Interface> getInteractsWith() {return  interactsWith.get(); }

        // get data holon properties

        public List<DataHolonProperty> getConsistsOf() {return  consistsOf.get(); }

        // get the Scenes that its used in

        public List<Scene> getUsedIn() {return  usedIn.get(); }

        // Setters

        public void setName(final String name){
            this.name = name;
        }

        public void setDescription(final String description){
            this.description = description;
        }

        public void setReferences(final List<DataHolon> references) {
            this.references = MorphiaReference.wrap(references);
        }

        public void setReferencedBy(final List<DataHolon> referencedBy) {
            this.referencedBy = MorphiaReference.wrap(referencedBy);
        }

        public void setIsclassificationOf(final List<DataHolonType> isaclassificationOf) {
            this.isaclassificationOf = MorphiaReference.wrap(isaclassificationOf);
        }

        public void setInteractsWith(final List<Interface> interactsWith) {
            this.interactsWith = MorphiaReference.wrap(interactsWith);
       }

        public void setConsistsOf(final List<DataHolonProperty> consistsOf) {
            this.consistsOf = MorphiaReference.wrap(consistsOf);
        }

        public void setUsedIn(final List<Scene> usedIn) {
            this.usedIn = MorphiaReference.wrap(usedIn);
        }

        // toString

        @Override
        public String toString() {
            return "DataHolon [id=" + id + ", name=" + name + ", description=" + description + "]";
        }

    }
johnda98 commented 3 years ago

the controller code snippet

        for (Interface interfaces : interfacesofholon) {
                dholondescfull.appendText( interfaces.getName() + " " + interfaces.getDescription() + " " + interfaces.getApiname() + "\n");
                dholondescfull.appendText(" \n");
                dholondescfull.appendText("Which is sourced from .. ");

                DataSource source = interfaces.getIsaccessFor();
                dholondescfull.appendText(source.getName() + " " + source.getDescription() + "\n");
                dholondescfull.appendText( " \n");
            }

        // Morphia null pointer - optionality of rel handling ??
                 List<Scene> inscenes = eg1holon.getUsedIn();
                  dholondescfull.appendText("This Data Holon is included in Scenes .. \n");
                  dholondescfull.appendText(" \n");
johnda98 commented 3 years ago

basically the dataholon entity is created without a 'scene' entity.. because that relationship is optional .. basic modelling.. all good so far.

johnda98 commented 3 years ago

Screenshot from 2020-10-15 12-53-31

johnda98 commented 3 years ago

so data holon 2 NYC1 was created without having a scene entity... but when I come to read it .. it trips over .. obviously because the List is not there ie the Array in mongo.. but surely Morphia should return null or for coder or indeed, Morphia, to be able to 'detect' if the scene Array is not in mongo BEFORE it trips w null pointer

johnda98 commented 3 years ago
Starting DataStore on DB: modsimv1
[DataHolon [id=5f77a307e74f033e954fa440, name=NYC Marshes - Future potential: CRE., description=Maps of NY Marshlands, with Ground Penetrating Radar & Salt Ingress timeseries], DataHolon [id=5f875ee96c28101dc2104b15, name=NYC1, description=NYC working copy]]
Oct 15, 2020 12:56:42 PM javafx.scene.CssStyleHelper calculateValue
WARNING: Caught 'java.lang.ClassCastException: class java.lang.Double cannot be cast to class javafx.scene.paint.Paint (java.lang.Double is in module java.base of loader 'bootstrap'; javafx.scene.paint.Paint is in module javafx.graphics of loader 'app')' while converting value for '-fx-background-color' from inline style on AnchorPane[id=AnchorPane, styleClass=root]
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1787)
    at javafx.fxml/javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1670)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3856)
    at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1851)
    at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2584)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:409)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:299)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:447)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:412)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:446)
    at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:556)
    at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:942)
    at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:277)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at com.sun.javafx.reflect.Trampoline.invoke(MethodUtil.java:76)
    at jdk.internal.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at javafx.base/com.sun.javafx.reflect.MethodUtil.invoke(MethodUtil.java:273)
    at javafx.fxml/com.sun.javafx.fxml.MethodHelper.invoke(MethodHelper.java:83)
    at javafx.fxml/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1784)
    ... 30 more
Caused by: java.lang.NullPointerException
    at org.crob/org.crob.DataHolon.getUsedIn(DataHolon.java:71)
    at org.crob/org.crob.PrimaryController.getholon(PrimaryController.java:974)
    at org.crob/org.crob.PrimaryController.dhicon1clickAction(PrimaryController.java:876)
    ... 41 more
johnda98 commented 3 years ago

line 974 is .. of course fine with data holons with 'scene' entities .. but its an optional relationship 0 to * .. many in UML

// Morphia null pointer - optionality of rel handling ?? line 974 List inscenes = eg1holon.getUsedIn();

johnda98 commented 3 years ago

Was thinking over lunch there must be some morphia method in MorphiaReference that I can call to detect if that Reference 'usedIn exists or not in mongo and thereby skip the code and therefore skip the null pointer exception trip

Welcome any kind advisement.. it has to be something obvious .. as referential integrity and optionality is core to any RDBMs or ODBMs

johnda98 commented 3 years ago

or maybe it s aMorphiaReference annotation.. not much out there on google .. except a non-related null pointer morphia bug years ago.. and not many 2.0.1 example code snippets out there also .. some but they dont cover all cases

evanchooly commented 3 years ago

The problem isn't really a Morphia issue, as such. You're trying to dereference that MorphiaReference field when it's null. The correct thing here is to do a null check before calling get(). Since there is nothing in the returned document(s) when you query the database, Morphia does nothing to that field and so it retains the null value given to it by the JVM when your instance was created.

johnda98 commented 3 years ago

Thanks Evan.. ok, I did explore that, will do so again and advise. so the ignoreMissing parm is for objects that are referenced but the document doesnt exist.. to handle broken integrity.

johnda98 commented 3 years ago

Ok nailed it.. i think.. your thoughts ? .. its procedural

got into Compass and added the Array and left it empty not null.

So.. in a Morphia datastore.save , the entity instance must have ALL MorphiaReference Lists set to empty.. in order to avoid NPEs when calling entity get methods to see if any optional collections exist or not.

soooo.. to set to Empty and not-null, prior to the Morphia .save .. all entity .set methods must be called with a 'new ArrayList<> ' instance to initialize

It would be nice if Morphia set(create) all MorphiaReference annotated Lists to empty if those Lists do not exist on mongo, for that instance, by default.. since there is no way of knowing(detecting) (from the App), that the List..( mongo Array ) is not there, since NPEs are thrown.. and yep poor practice to catch NPEs or to have a need to. :-) thoughts ?

evanchooly commented 3 years ago

You shouldn't get an NPE on a save if the reference is null. If you do see that, please file an issue and I'll fix that.

But, yes, ignoreMissing is what to do when you have an active reference but the referenced document doesn't exist. I've debated init'ing those MorphiaReference instance with "empty" values but it violates Morphia's general flow of serialization. If a field isn't found in the fetched document, Morphia doesn't even consider that field at all.

In general, you'd solve your get problem something like this:

public List<Scene> getUsedIn() {
    return usedIn != null ? usedIn.get() : emptyList(); 
}

Then you always get a legitimately iterable return value but because emptlyList() returns an immutable List, you couldn't accidentally add to that List. Then you'd just need to provide an addScene() that would do the appropriate null checks and initializations to create a populated MorphiaReference. I'll update the docs with an example of this once I wrap up what I have going.

johnda98 commented 3 years ago

np .. will close.. yes solved by ALWAYS creating an empty array and save the entity.. even if no relationship to store in the List.. Morphia could possible do this under the covers for ALL entity saves.. and it may help any NPEs being avoided if a coder checks a List but its not there. No I have to get back to the Update issue. as no Docs or examples on that for 2.0.1.. well there is but the examples include depreacted methods