I have an strange behavior. Consider this code:
Every object that is returned from the database is created by this proxy:
public static <T> T bbcreate(Class<T> c, OElement ov, Transaction transaction ) {
LOGGER.log(Level.FINEST, "create proxy for class: "+c+(c!=null?c.getName().toString():"NULL CLASS!!!!"));
T po = null;
try {
Class<?> type = cache.findOrInsert(c.getClassLoader(), c, () -> {
return
new ByteBuddy(ClassFileVersion.ofThisVm())
.subclass(c)
.defineField("___ogm___interceptor", ObjectProxy.class, Visibility.PUBLIC)
.implement(IObjectProxy.class)
.defineConstructor(Visibility.PUBLIC)
.withParameter(ObjectProxy.class)
.intercept(FieldAccessor.ofField("___ogm___interceptor").setsArgumentAt(0)
.andThen(MethodCall.invoke(c.getDeclaredConstructor()))
)
.method(ElementMatchers.any()) // isDeclaredBy(ITest.class)
.intercept(MethodDelegation // This.class,Origin.class,AllArguments.class,SuperMethod.class)
.withDefaultConfiguration() //.withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS)
.filter(ElementMatchers.named("intercept"))
.toField("___ogm___interceptor")) // MethodDelegation.to(bbi)
.make()
.load(c.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
});
// crear el proxy al que delegar las llamadas
ObjectProxy bbi = new ObjectProxy(c,ov,transaction);
// crear una instancia
po = (T)type.getConstructor(ObjectProxy.class).newInstance(bbi);
bbi.___setProxiedObject(po);
// clean possible dirtiness (because of actions in default constructor)
bbi.___removeDirtyMark();
} catch (InstantiationException | IllegalAccessException | SecurityException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(ObjectProxyFactory.class.getName()).log(Level.SEVERE, null, ex);
}
return po;
}
The ObjectProxy's intercept method that is called is this:
// ByteBuddy interceptor
@RuntimeType
public Object intercept(@This Object self,
@Origin Method method,
@AllArguments Object[] args,
@SuperMethod(nullIfImpossible = true) Method superMethod,
@Empty Object defaultValue
) throws Throwable {
//
// response object
Object res = null;
//this.___proxiedObject = self;
// el estado del objeto se debe poder consultar siempre
//=====================================================
LOGGER.log(Level.FINEST, "=====================================================");
LOGGER.log(Level.FINEST, self.getClass().getName() + " : "+ this.___baseElement.getRecord().getIdentity()+ " > method: "+method.getName()+" superMethod: "+(superMethod!=null?superMethod.getName():"NULL"));
LOGGER.log(Level.FINEST, "=====================================================");
String debugLabel = this.___baseElement.getRecord().getIdentity()+ " > method: "+method.getName();
if (method.getName().equals("___isValid")) {
return this.___isValid();
}
if (method.getName().equals("___isDeleted")) {
return this.___isDeleted();
}
if (method.getName().equals("___getVertex")) {
return this.___getVertex();
}
if (method.getName().equals("___getEdge")) {
return this.___getEdge();
}
if (method.getName().equals("___getElement")) {
return this.___getElement();
}
if (method.getName().equals("___getRid")) {
return this.___getRid();
}
if (method.getName().equals("___getBaseClass")) {
return this.___getBaseClass();
}
if (!this.___isValidObject) {
LOGGER.log(Level.FINER, "El objeto está marcado como inválido!!!");
throw new InvalidObjectReference(this.___transaction);
}
if (method.getName().equals("___rollback")) {
if (this.___objectReady) {
this.___rollback();
return true;
}
}
if (this.___baseElement.getIdentity().isNew()) {
LOGGER.log(Level.FINER, "RID nuevo. No procesar porque el store preparó todo y no hay nada que recuperar de la base.");
this.___loadLazyLinks = false;
}
//if object was deleted:
if (this.___deletedMark) {
switch (method.getName()) {
case "equals":
case "hashCode":
if (!this.___transaction.getSessionManager().getConfig().
isEqualsAndHashCodeOnDeletedThrowsException()) {
return superMethod.invoke(self, args);
}
default:
throw new ObjectMarkedAsDeleted("The object " + this.___baseElement.getIdentity().toString() +
" was deleted from the database. Trying to call to " + method.getName(),
this.___transaction);
}
}
// modificar el llamado
switch (method.getName()) {
case "___uptadeVersion":
if (this.___objectReady) {
this.___uptadeVersion();
}
break;
case "___injectRid":
if (this.___objectReady) {
this.___injectRid();
}
break;
case "___getProxiedObject":
if (this.___objectReady) {
res = this.___getProxiedObject();
}
break;
case "___loadLazyLinks":
if (this.___objectReady) {
this.___loadLazyLinks();
}
break;
case "___eagerLoad":
if (this.___objectReady) {
this.___eagerLoad();
}
break;
case "___fullLoad":
if (this.___objectReady) {
this.___fullLoad();
}
break;
case "___isDirty":
if (this.___objectReady) {
res = this.___isDirty();
}
break;
case "___setDirty":
if (this.___objectReady) {
this.___setDirty();
}
break;
case "___removeDirtyMark":
if (this.___objectReady) {
this.___removeDirtyMark();
}
break;
case "___commit":
/**
* FIXME: se podría evitar si se controlara si los links se
* han cargado o no al momento de hacer el commit para
* evitar realizar el load sin necesidad.
*/
if (this.___objectReady) {
if (this.___loadLazyLinks) {
this.___loadLazyLinks();
}
this.___commit();
}
break;
case "___reload":
if (this.___objectReady) {
this.___reload();
}
break;
case "___commitSuccessful":
if (this.___objectReady) {
this.___commitSuccessful();
}
break;
case "___setDeletedMark":
this.___setDeletedMark();
break;
case "___updateElement":
this.___updateElement();
break;
case "___setEdge":
this.___setEdge((OEdge)args[0]);
break;
case "___setVertex":
this.___setVertex((OVertex)args[0]);
break;
case "___setAuditLogLabel":
this.___setAuditLogLabel((String)args[0]);
break;
case "___getAuditLogLabel":
res = this.___getAuditLogLabel();
break;
case "___ogm___setDirty":
res = superMethod.invoke(self, args);
break;
case "___ogm___isDirty":
res = superMethod.invoke(self, args);
break;
default:
// invoke the method on the real object with the given params:
if (method.getName().equals("toString")) {
try {
//if object doesn't have toString defined, we implement one on the fly
ReflectionUtils.findMethod(this.___baseClass, "toString", (Class<?>[]) null);
} catch (NoSuchMethodException nsme) {
res = this.___baseElement.getIdentity().toString(); //returns rid
break;
}
}
if (this.___loadLazyLinks) {
boolean methodTriggersLoadLazyLink = true;
if (method.getName().equals("equals") || method.getName().equals("hashCode")) {
methodTriggersLoadLazyLink &= this.___transaction.getSessionManager().
getConfig().isEqualsAndHashCodeTriggerLoadLazyLinks();
}
methodTriggersLoadLazyLink &= !method.isAnnotationPresent(DontLoadLinks.class);
if (this.___objectReady && methodTriggersLoadLazyLink) {
this.___loadLazyLinks();
}
}
if (superMethod == null) {
System.out.println("superMethod == NULL !!!!");
return defaultValue;
}
try {
res = superMethod.invoke(self, args);
} catch (InvocationTargetException ex) {
throw ex.getCause();
}
// verificar si hay diferencias entre los objetos dependiendo de la estrategia seleccionada.
if (this.___objectReady) {
switch (this.___transaction.getSessionManager().getActivationStrategy()) {
case CLASS_INSTRUMENTATION:
// si se está usando la instrumentación de clase, directamente verificar en el objeto
// cual es su estado.
LOGGER.log(Level.FINEST, "o: {0} ITrans: {1}", new Object[]{self.getClass().getName(), self instanceof ITransparentDirtyDetector});
if (((ITransparentDirtyDetector) self).___ogm___isDirty()) {
LOGGER.log(Level.FINEST, "objeto {0} marcado como dirty por ASM. Agregarlo a la lista de pendientes.", self.getClass().getName());
this.___setDirty();
}
}
}
break;
}
// return the result
LOGGER.log(Level.FINEST, "<<<<<<<<<<<<<<<<<<<<< INTERCEPT END: "+debugLabel);
return res;
}
SimpleVertex test object:
package test;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Logger;
import net.odbogm.annotations.Audit;
import net.odbogm.annotations.Entity;
import net.odbogm.annotations.Ignore;
import net.odbogm.annotations.RID;
import net.odbogm.annotations.Sequence;
import net.odbogm.annotations.Version;
/**
*
* @author Marcelo D. Ré {@literal <marcelo.re@gmail.com>}
*/
@Entity
@Audit(log = Audit.AuditType.DELETE)
public class SimpleVertex implements Comparable<SimpleVertex>{
@Ignore
private final static Logger LOGGER = Logger.getLogger(SimpleVertex.class .getName());
@RID private String rid;
@Version private int version = -1;
@Sequence(sequenceName = "test_sequence") private final Long serial = null;
private String uuid;
private String s;
public int i;
private float f;
private boolean b;
private Date fecha;
private Integer oI;
private Float oF;
private Boolean oB;
public Date getFecha() {
return fecha;
}
public void setFecha(Date fecha) {
this.fecha = fecha;
}
public int getI() {
return i;
}
public float getF() {
return f;
}
public boolean isB() {
return b;
}
public Integer getoI() {
return oI;
}
public Float getoF() {
return oF;
}
public Boolean getoB() {
return oB;
}
public Long getSerial() {
return serial;
}
public SimpleVertex(String s, int i, float f, boolean b, Integer oI, Float oF, Boolean oB) {
this.s = s;
this.i = i;
this.f = f;
this.b = b;
this.oI = oI;
this.oF = oF;
this.oB = oB;
this.uuid = UUID.randomUUID().toString();
}
public SimpleVertex(){
this.s = "string";
this.i = 1;
this.f = 0.1f;
this.b = true;
this.oI = 100;
this.oF = 1.1f;
this.oB = true;
this.uuid = UUID.randomUUID().toString();
}
public SimpleVertex(String s) {
super();
this.s = s;
}
public String getRid() {
return rid;
}
public int getVersion() {
return version;
}
public String getS(){
return this.s;
}
public void setRid(String rid) {
this.rid = rid;
}
public void setS(String s) {
this.s = s;
}
public void testSVMethod() {
System.out.println("in SV");
}
// @Override
// public String toString() {
// return this.s + " - " + this.b + " - " + this.i + " - " + this.f;
// }
public void setI(int i) {
this.i = i;
}
public void setF(float f) {
this.f = f;
}
public void setB(boolean b) {
this.b = b;
}
public void setoI(Integer oI) {
this.oI = oI;
}
public void setoF(Float oF) {
this.oF = oF;
}
public void setoB(Boolean oB) {
this.oB = oB;
}
public String getUUID() {
return this.uuid;
}
// @Override
// public int hashCode() {
// int hash = 7;
// return hash;
// }
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SimpleVertex other = (SimpleVertex) obj;
return Objects.equals(this.uuid, other.uuid);
}
@Override
public int compareTo(SimpleVertex t) {
return this.uuid.compareTo(t.getUUID());
}
}
and SimpleVertexEx test object:
package test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Logger;
import net.odbogm.annotations.Audit;
import net.odbogm.annotations.CascadeDelete;
import net.odbogm.annotations.Entity;
import net.odbogm.annotations.FieldAttributes;
import net.odbogm.annotations.Ignore;
import net.odbogm.annotations.Indexed;
import net.odbogm.annotations.RemoveOrphan;
import net.odbogm.annotations.DontLoadLinks;
import net.odbogm.annotations.Eager;
import net.odbogm.annotations.Indirect;
/**
*
* @author Marcelo D. Ré {@literal <marcelo.re@gmail.com>}
*/
@Entity
@Audit(log = Audit.AuditType.ALL)
public class SimpleVertexEx extends SimpleVertex {
@Ignore
private final static Logger LOGGER = Logger.getLogger(SimpleVertexEx.class .getName());
@FieldAttributes(mandatory = FieldAttributes.Bool.TRUE)
private String svex;
private SimpleVertexEx looptest;
public EnumTest enumTest;
@Indexed(type = Indexed.IndexType.UNIQUE)
private String svuuid;
@RemoveOrphan
public SimpleVertex svinner;
public List<String> lString;
public ArrayList<String> alString;
public Map<String, String> mString;
public HashMap<String, String> hmString;
@RemoveOrphan
@CascadeDelete
public ArrayList<SimpleVertex> alSV;
public List<SimpleVertex> lSV;
public ArrayList<SimpleVertexEx> alSVE;
@CascadeDelete
public HashMap<String, SimpleVertex> hmSV;
public HashMap<String, SimpleVertexEx> hmSVE;
public HashMap<EdgeAttrib, SimpleVertexEx> ohmSVE;
@Indirect(linkName = "SimpleVertexEx_looptest")
private SimpleVertexEx indirectLoopTest;
@Eager
private SimpleVertexEx eagerTest;
@Indirect(linkName = "SimpleVertexEx_eagerTest")
private SimpleVertexEx indirectEagerTest;
@Eager
@Indirect(linkName = "SimpleVertexEx_eagerTest")
private SimpleVertexEx eagerIndirectEagerTest;
public SimpleVertexEx(String svex, String s, int i, float f, boolean b, Integer oI, Float oF, Boolean oB) {
super(s, i, f, b, oI, oF, oB);
this.svex = svex;
this.enumTest = EnumTest.UNO;
this.svuuid = UUID.randomUUID().toString();
}
public SimpleVertexEx() {
this(null, "default");
}
public SimpleVertexEx(String s) {
this(s, "default");
}
public SimpleVertexEx(String s, String svex) {
super(s);
this.svex = svex;
this.svuuid = UUID.randomUUID().toString();
}
public void initEnum() {
this.enumTest = EnumTest.UNO;
}
public void initArrayListString() {
this.alString = new ArrayList<>();
this.alString.add("String 1");
this.alString.add("String 2");
this.alString.add("String 3");
}
public void initHashMapString() {
this.hmString = new HashMap<>();
this.hmString.put("hmString 1", "hmString 1");
this.hmString.put("hmString 1", "hmString 2");
this.hmString.put("hmString 1", "hmString 3");
}
public void initArrayList(){
this.alSV = new ArrayList<SimpleVertex>();
this.alSV.add(new SimpleVertex());
this.alSV.add(new SimpleVertex());
this.alSV.add(new SimpleVertex());
}
public void initHashMap() {
this.hmSV = new HashMap<String, SimpleVertex>();
SimpleVertex sv = new SimpleVertex();
this.hmSV.put("key1", sv);
this.hmSV.put("key2", sv);
this.hmSV.put("key3", new SimpleVertex());
}
public HashMap<String, SimpleVertexEx> getHmSVE() {
return hmSVE;
}
public void setHmSVE(HashMap<String, SimpleVertexEx> hmSVE) {
this.hmSVE = hmSVE;
}
public HashMap<EdgeAttrib, SimpleVertexEx> getOhmSVE() {
return ohmSVE;
}
public void setOhmSVE(HashMap<EdgeAttrib, SimpleVertexEx> ohmSVE) {
this.ohmSVE = ohmSVE;
}
public void initInner() {
this.svinner = new SimpleVertex();
this.svinner.setS("sv inner");
}
public void testSVEXMethod() {
System.out.println("in SVEx");
}
public void setSvinner(SimpleVertex svinner) {
this.svinner = svinner;
}
public void setSvex(String s) {
this.svex = s;
}
public String getSvex() {
return svex;
}
public EnumTest getEnumTest() {
return enumTest;
}
public void setEnumTest(EnumTest e) {
this.enumTest = e;
}
public SimpleVertex getSvinner() {
return svinner;
}
public ArrayList<SimpleVertex> getAlSV() {
return alSV;
}
public ArrayList<SimpleVertexEx> getAlSVE() {
return alSVE;
}
public void setAlSVE(ArrayList<SimpleVertexEx> alSVE) {
this.alSVE = alSVE;
}
public HashMap<String, SimpleVertex> getHmSV() {
return hmSV;
}
public SimpleVertexEx getLooptest() {
return looptest;
}
@DontLoadLinks
public SimpleVertexEx getLooptestLinkNotLoaded() {
return looptest;
}
public void setLooptest(SimpleVertexEx looptest) {
this.looptest = looptest;
}
@DontLoadLinks
public SimpleVertexEx getEagerTest() {
return eagerTest;
}
public void setEagerTest(SimpleVertexEx eagerTest) {
this.eagerTest = eagerTest;
}
@DontLoadLinks
public SimpleVertexEx getIndirectLoopTestDontLoad() {
return indirectLoopTest;
}
public SimpleVertexEx getIndirectLoopTest() {
return indirectLoopTest;
}
@DontLoadLinks
public SimpleVertexEx getIndirectEagerTest() {
return indirectEagerTest;
}
@DontLoadLinks
public SimpleVertexEx getEagerIndirectEagerTest() {
return eagerIndirectEagerTest;
}
public String getUuid() {
return this.svuuid;
}
public void setUuid(String uuid) {
this.svuuid = uuid;
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + Objects.hashCode(this.svuuid);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!SimpleVertexEx.class.isInstance(obj)) {
return false;
}
final SimpleVertexEx other = (SimpleVertexEx) obj;
return Objects.equals(this.svuuid, other.svuuid);
}
}
Now, when I use that's objects:
var res = sm.query(SimpleVertexEx.class, "where out('SimpleVertexEx_alSV').size() != 0 limit 1");
SimpleVertexEx sve = res.get(0);
SimpleVertex sv1 = sve.getAlSV().get(0);
SimpleVertex sv2 = sve.getAlSV().get(1);
SimpleVertex sv3 = sve.getAlSV().get(2);
System.out.println("sv1: "+sv1.getRid());
System.out.println("sv2: "+sv2.getRid());
System.out.println("sv3: "+sv3.getRid());
System.out.println(""+sv1.compareTo(sv2));
System.out.println(""+sv2.compareTo(sv3));
var falla = sve.getAlSV().stream().sorted().collect(Collectors.toList());
All the lines works but the last fail. Sort call compareTo on the sv's but the proxy get a superMethod = null. Why? In the previous lines the call work well. This is what the interceptor log:
MÁS DETALLADO[2022/08/17 10:21:34]:net.odbogm.proxy.ObjectProxy intercept :: =====================================================
MÁS DETALLADO[2022/08/17 10:21:34]:net.odbogm.proxy.ObjectProxy intercept :: test.SimpleVertex$ByteBuddy$ghrAs2Lb : #414:0 > method: compareTo superMethod: compareTo$accessor$XL9qkaa5
MÁS DETALLADO[2022/08/17 10:21:34]:net.odbogm.proxy.ObjectProxy intercept :: =====================================================
MÁS DETALLADO[2022/08/17 11:02:21]:net.odbogm.proxy.ObjectProxy intercept :: =====================================================
MÁS DETALLADO[2022/08/17 11:02:21]:net.odbogm.proxy.ObjectProxy intercept :: test.SimpleVertex$ByteBuddy$rMJgDCTS : #413:0 > method: compareTo superMethod: compareTo$accessor$q70euKMf
MÁS DETALLADO[2022/08/17 11:02:21]:net.odbogm.proxy.ObjectProxy intercept :: =====================================================
MÁS DETALLADO[2022/08/17 10:21:34]:net.odbogm.proxy.ObjectProxy intercept :: =====================================================
MÁS DETALLADO[2022/08/17 10:21:34]:net.odbogm.proxy.ObjectProxy intercept :: test.SimpleVertex$ByteBuddy$ghrAs2Lb : #413:0 > method: compareTo superMethod: NULL
MÁS DETALLADO[2022/08/17 10:21:34]:net.odbogm.proxy.ObjectProxy intercept :: =====================================================
superMethod == NULL !!!!
At some point it miss the method but I could not reproduce it as a simple script.