[FEAT] add capability to use @ManyToManyLocal that is an implementation compatible with NoSql MAny-ToMany link

This feature manage to update the 2 side of the local stored of the data
This commit is contained in:
Edouard DUPIN 2025-04-22 11:30:58 +02:00
parent 0cf718d415
commit b530cb629b
7 changed files with 1113 additions and 2 deletions

View File

@ -0,0 +1,27 @@
package org.atriasoft.archidata.annotation;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* In NoSql entity the relation is stored in the 2 part of the entity,
* then it is needed to define the field that store the relation data value in the remote elements.
*/
@Retention(RUNTIME)
@Target({ FIELD, METHOD })
public @interface ManyToManyLocal {
/**
* The entity class that is the target of the
* association.
*/
Class<?> targetEntity();
/**
* The field that owns the revert value. empty if the relationship is unidirectional.
*/
String remoteField() default "";
}

View File

@ -29,6 +29,7 @@ import org.atriasoft.archidata.annotation.CreationTimestamp;
import org.atriasoft.archidata.annotation.UpdateTimestamp;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnManyToMany;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnManyToManyLocal;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnManyToOne;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnOneToMany;
import org.atriasoft.archidata.dataAccess.addOnSQL.DataAccessAddOn;
@ -70,7 +71,9 @@ public class DBAccessSQL extends DBAccess {
new AddOnManyToMany(), //
new AddOnManyToOne(), //
new AddOnOneToMany(), //
new AddOnDataJson());
new AddOnDataJson(), //
new AddOnManyToManyLocal());
private final DbIoSql db;
public DBAccessSQL(final DbIoSql db) throws IOException {
@ -1239,7 +1242,6 @@ public class DBAccessSQL extends DBAccess {
query.append(" ");
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
condition.whereAppendQuery(query, tableName, null, deletedFieldName);
// If the first field is not set, then nothing to update n the main base:
if (!firstField) {
LOGGER.debug("generate update query: '{}'", query.toString());

View File

@ -0,0 +1,464 @@
package org.atriasoft.archidata.dataAccess.addOnSQL;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.AnnotationTools;
import org.atriasoft.archidata.annotation.AnnotationTools.FieldName;
import org.atriasoft.archidata.annotation.ManyToManyLocal;
import org.atriasoft.archidata.dataAccess.CountInOut;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.DBAccessSQL;
import org.atriasoft.archidata.dataAccess.DataFactory;
import org.atriasoft.archidata.dataAccess.LazyGetter;
import org.atriasoft.archidata.dataAccess.QueryInList;
import org.atriasoft.archidata.dataAccess.QueryOptions;
import org.atriasoft.archidata.dataAccess.addOnSQL.model.TableCoversGeneric;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.dataAccess.options.OptionRenameColumn;
import org.atriasoft.archidata.dataAccess.options.OptionSpecifyType;
import org.atriasoft.archidata.dataAccess.options.OverrideTableName;
import org.atriasoft.archidata.exception.SystemException;
import org.atriasoft.archidata.tools.ContextGenericTools;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.validation.constraints.NotNull;
public class AddOnManyToManyLocal implements DataAccessAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToManyLocal.class);
static final String SEPARATOR_LONG = "-";
static final String SEPARATOR_UUID = "_";
@Override
public Class<?> getAnnotationClass() {
return ManyToManyLocal.class;
}
@Override
public boolean isCompatibleField(final Field field) {
if (field.getType() != List.class) {
return false;
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
return true;
}
final ManyToManyLocal decorators = field.getDeclaredAnnotation(ManyToManyLocal.class);
if (decorators == null) {
return false;
}
if (decorators.targetEntity() == objectClass) {
return true;
}
return false;
}
@Override
public void insertData(
final DBAccessSQL ioDb,
final PreparedStatement ps,
final Field field,
final Object rootObject,
final CountInOut iii)
throws SQLException, IllegalArgumentException, IllegalAccessException, JsonProcessingException {
final Object data = field.get(rootObject);
if (data == null) {
ps.setNull(iii.value, Types.VARCHAR);
}
final ObjectMapper objectMapper = ContextGenericTools.createObjectMapper();
final String dataString = objectMapper.writeValueAsString(data);
ps.setString(iii.value, dataString);
iii.inc();
}
@Override
public boolean isUpdateAsync(final Field field) {
return true;
}
@Override
public void asyncUpdate(
final DBAccessSQL ioDb,
final Object previousData,
final String tableName,
final Object primaryKeyValue,
final Field field,
final Object insertedData,
final List<LazyGetter> actions,
final QueryOptions options) throws Exception {
final Object previousDataValue = field.get(previousData);
Collection<?> previousDataCollection = new ArrayList<>();
if (previousDataValue instanceof final Collection<?> tmpCollection) {
previousDataCollection = tmpCollection;
}
final Object insertedDataValue = insertedData;
Collection<?> insertedDataCollection = new ArrayList<>();
if (insertedDataValue instanceof final Collection<?> tmpCollection) {
insertedDataCollection = tmpCollection;
}
// add new Values
for (final Object value : insertedDataCollection) {
if (previousDataCollection.contains(value)) {
continue;
}
actions.add(() -> {
addLinkRemote(ioDb, field, primaryKeyValue, value);
});
}
// remove old values:
for (final Object value : previousDataCollection) {
if (insertedDataCollection.contains(value)) {
continue;
}
actions.add(() -> {
removeLinkRemote(ioDb, field, primaryKeyValue, value);
});
}
}
/** Some action must be done asynchronously for update or remove element
* @param field
* @return */
@Override
public boolean isInsertAsync(final Field field) throws Exception {
return true;
}
/** When insert is mark async, this function permit to create or update the data
* @param tableName Name of the Table.
* @param localId Local ID of the current table
* @param field Field that is updated.
* @param data Data that might be inserted.
* @param actions Asynchronous action to do after main request. */
@Override
public void asyncInsert(
final DBAccessSQL ioDb,
final String tableName,
final Object primaryKeyValue,
final Field field,
final Object data,
final List<LazyGetter> actions,
final QueryOptions options) throws Exception {
final Object insertedData = data;
if (insertedData == null) {
return;
}
if (insertedData instanceof final Collection<?> insertedDataCollection) {
for (final Object value : insertedDataCollection) {
actions.add(() -> {
addLinkRemote(ioDb, field, primaryKeyValue, value);
});
}
}
}
@Override
public boolean isPreviousDataNeeded(final Field field) {
return true;
}
@Override
public boolean canInsert(final Field field) {
return isCompatibleField(field);
}
@Override
public boolean canRetrieve(final Field field) {
return isCompatibleField(field);
}
@Override
public void generateQuery(
@NotNull final String tableName,
@NotNull final String primaryKey,
@NotNull final Field field,
@NotNull final StringBuilder querySelect,
@NotNull final StringBuilder query,
@NotNull final String name,
@NotNull final CountInOut count,
final QueryOptions options) throws Exception {
querySelect.append(" ");
querySelect.append(tableName);
querySelect.append(".");
querySelect.append(name);
count.inc();
}
@Override
public void fillFromQuery(
final DBAccessSQL ioDb,
final ResultSet rs,
final Field field,
final Object data,
final CountInOut count,
final QueryOptions options,
final List<LazyGetter> lazyCall) throws Exception {
if (field.getType() != List.class) {
throw new SystemException("@ManyToManyLocal must contain a List");
}
final String jsonData = rs.getString(count.value);
count.inc();
if (rs.wasNull()) {
return;
}
final ObjectMapper objectMapper = ContextGenericTools.createObjectMapper();
final ParameterizedType listType = (ParameterizedType) field.getGenericType();
final Class<?> objectClass = (Class<?>) listType.getActualTypeArguments()[0];
if (objectClass == Long.class) {
final List<Long> dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<Long>>() {});
field.set(data, dataParsed);
return;
}
if (objectClass == String.class) {
final List<String> dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<String>>() {});
field.set(data, dataParsed);
return;
}
if (objectClass == UUID.class)
{
final List<UUID> dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<UUID>>() {});
field.set(data, dataParsed);
return;
}
if (objectClass == ObjectId.class) {
final List<ObjectId> dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<ObjectId>>() {});
field.set(data, dataParsed);
return;
}
final ManyToManyLocal decorators = field.getDeclaredAnnotation(ManyToManyLocal.class);
if (decorators == null) {
return;
}
if (objectClass == decorators.targetEntity()) {
final Class<?> foreignKeyType = AnnotationTools.getPrimaryKeyField(objectClass).getType();
if (foreignKeyType == Long.class) {
final List<Long> idList = objectMapper.readValue(jsonData, new TypeReference<List<Long>>() {});
if (idList != null && idList.size() > 0) {
final FieldName idField = AnnotationTools.getFieldName(AnnotationTools.getIdField(objectClass),
options);
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
// TODO: update to have get with abstract types ....
final Object foreignData = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryInList<>(idField.inTable(), idList)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
} else if (foreignKeyType == UUID.class) {
final List<UUID> idList = objectMapper.readValue(jsonData, new TypeReference<List<UUID>>() {});
if (idList != null && idList.size() > 0) {
final FieldName idField = AnnotationTools.getFieldName(AnnotationTools.getIdField(objectClass),
options);
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
final List<UUID> childs = new ArrayList<>(idList);
// TODO: update to have get with abstract types ....
final Object foreignData = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryInList<>(idField.inTable(), childs)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
} else if (foreignKeyType == ObjectId.class) {
final List<ObjectId> idList = objectMapper.readValue(jsonData, new TypeReference<List<ObjectId>>() {});
if (idList != null && idList.size() > 0) {
final FieldName idField = AnnotationTools.getFieldName(AnnotationTools.getIdField(objectClass),
options);
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
final List<ObjectId> childs = new ArrayList<>(idList);
// TODO: update to have get with abstract types ....
final Object foreignData = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryInList<>(idField.inTable(), childs)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
}
}
}
@Override
public void createTables(
final String tableName,
final Field primaryField,
final Field field,
final StringBuilder mainTableBuilder,
final List<String> preActionList,
final List<String> postActionList,
final boolean createIfNotExist,
final boolean createDrop,
final int fieldId,
final QueryOptions options) throws Exception {
// store data as json to response like a no-sql
DataFactory.createTablesSpecificType(tableName, primaryField, field, mainTableBuilder, preActionList,
postActionList, createIfNotExist, createDrop, fieldId, JsonValue.class, options);
}
private static void addLinkLocal(
final DBAccess ioDb,
final Class<?> clazz,
final String clazzPrimaryKeyName,
final Object clazzPrimaryKeyValue,
final String fieldNameToUpdate,
final Object valueToAdd) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
final QueryOptions options = new QueryOptions(new OverrideTableName(tableName),
new OptionSpecifyType("idOfTheObject", clazzPrimaryKeyValue.getClass()),
new OptionSpecifyType("filedNameOfTheObject", valueToAdd.getClass(), true));
options.add(new OptionRenameColumn("idOfTheObject", clazzPrimaryKeyName));
options.add(new OptionRenameColumn("filedNameOfTheObject", fieldNameToUpdate));
final TableCoversGeneric data = ioDb.get(TableCoversGeneric.class, clazzPrimaryKeyValue, options.getAllArray());
if (data.filedNameOfTheObject == null) {
data.filedNameOfTheObject = new ArrayList<>();
}
for (final Object elem : data.filedNameOfTheObject) {
if (elem.equals(valueToAdd)) {
return;
}
}
data.filedNameOfTheObject.add(valueToAdd);
ioDb.update(data, data.idOfTheObject, List.of("filedNameOfTheObject"), options.getAllArray());
}
public static void addLink(
final DBAccess ioDb,
final Class<?> clazz,
final Object clazzPrimaryKeyValue,
final String fieldNameToUpdate,
final Object valueToAdd) throws Exception {
final Field localField = AnnotationTools.getFieldNamed(clazz, fieldNameToUpdate);
{
//get local field to find the remote field name:
final Field primaryKeyField = AnnotationTools.getPrimaryKeyField(clazz);
final FieldName primaryKeyColomnName = AnnotationTools.getFieldName(primaryKeyField, null);
final FieldName localFieldName = AnnotationTools.getFieldName(localField, null);
addLinkLocal(ioDb, clazz, primaryKeyColomnName.inTable(), clazzPrimaryKeyValue, localFieldName.inTable(),
valueToAdd);
}
addLinkRemote(ioDb, localField, clazzPrimaryKeyValue, valueToAdd);
}
private static void addLinkRemote(
final DBAccess ioDb,
final Field localField,
final Object localPrimaryKeyValue,
final Object remotePrimaryKeyValue) throws Exception {
final ManyToManyLocal manyLocal = AnnotationTools.get(localField, ManyToManyLocal.class);
// Update the remote elements:
if (manyLocal == null || manyLocal.targetEntity() == null || manyLocal.remoteField() == null
|| manyLocal.remoteField().isEmpty()) {
return;
}
{
//get local field to find the remote field name:
final Field primaryKeyField = AnnotationTools.getPrimaryKeyField(manyLocal.targetEntity());
final FieldName primaryKeyColomnName = AnnotationTools.getFieldName(primaryKeyField, null);
final Field remoteField = AnnotationTools.getFieldNamed(manyLocal.targetEntity(), manyLocal.remoteField());
final FieldName localFieldName = AnnotationTools.getFieldName(remoteField, null);
addLinkLocal(ioDb, manyLocal.targetEntity(), primaryKeyColomnName.inTable(), remotePrimaryKeyValue,
localFieldName.inTable(), localPrimaryKeyValue);
}
}
private static void removeLinkLocal(
final DBAccess ioDb,
final Class<?> clazz,
final String clazzPrimaryKeyName,
final Object clazzPrimaryKeyValue,
final String fieldNameToUpdate,
final Object valueToRemove) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
final QueryOptions options = new QueryOptions(new OverrideTableName(tableName),
new OptionSpecifyType("idOfTheObject", clazzPrimaryKeyValue.getClass()),
new OptionSpecifyType("filedNameOfTheObject", valueToRemove.getClass(), true));
options.add(new OptionRenameColumn("idOfTheObject", clazzPrimaryKeyName));
options.add(new OptionRenameColumn("filedNameOfTheObject", fieldNameToUpdate));
final TableCoversGeneric data = ioDb.get(TableCoversGeneric.class, clazzPrimaryKeyValue, options.getAllArray());
if (data.filedNameOfTheObject == null) {
return;
}
final List<Object> newList = new ArrayList<>();
for (final Object elem : data.filedNameOfTheObject) {
if (elem.equals(valueToRemove)) {
continue;
}
newList.add(elem);
}
data.filedNameOfTheObject = newList;
if (data.filedNameOfTheObject.isEmpty()) {
data.filedNameOfTheObject = null;
}
ioDb.update(data, data.idOfTheObject, List.of("filedNameOfTheObject"), options.getAllArray());
}
public static void removeLink(
final DBAccess ioDb,
final Class<?> clazz,
final Object clazzPrimaryKeyValue,
final String fieldNameToUpdate,
final Object valueToRemove) throws Exception {
final Field localField = AnnotationTools.getFieldNamed(clazz, fieldNameToUpdate);
{
//get local field to find the remote field name:
final Field primaryKeyField = AnnotationTools.getPrimaryKeyField(clazz);
final FieldName primaryKeyColomnName = AnnotationTools.getFieldName(primaryKeyField, null);
final FieldName localFieldName = AnnotationTools.getFieldName(localField, null);
removeLinkLocal(ioDb, clazz, primaryKeyColomnName.inTable(), clazzPrimaryKeyValue, localFieldName.inTable(),
valueToRemove);
}
removeLinkRemote(ioDb, localField, clazzPrimaryKeyValue, valueToRemove);
}
private static void removeLinkRemote(
final DBAccess ioDb,
final Field localField,
final Object localPrimaryKeyValue,
final Object remotePrimaryKeyValue) throws Exception {
final ManyToManyLocal manyLocal = AnnotationTools.get(localField, ManyToManyLocal.class);
// Update the remote elements:
if (manyLocal == null || manyLocal.targetEntity() == null || manyLocal.remoteField() == null
|| manyLocal.remoteField().isEmpty()) {
return;
}
{
//get local field to find the remote field name:
final Field primaryKeyField = AnnotationTools.getPrimaryKeyField(manyLocal.targetEntity());
final FieldName primaryKeyColomnName = AnnotationTools.getFieldName(primaryKeyField, null);
final Field remoteField = AnnotationTools.getFieldNamed(manyLocal.targetEntity(), manyLocal.remoteField());
final FieldName localFieldName = AnnotationTools.getFieldName(remoteField, null);
removeLinkLocal(ioDb, manyLocal.targetEntity(), primaryKeyColomnName.inTable(), remotePrimaryKeyValue,
localFieldName.inTable(), localPrimaryKeyValue);
}
}
}

View File

@ -0,0 +1,563 @@
package test.atriasoft.archidata.dataAccess;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.atriasoft.archidata.dataAccess.DBAccessSQL;
import org.atriasoft.archidata.dataAccess.DataFactory;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnManyToManyLocal;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import test.atriasoft.archidata.ConfigureDb;
import test.atriasoft.archidata.StepwiseExtension;
import test.atriasoft.archidata.dataAccess.model.TypeManyToManyLocalOIDRemote;
import test.atriasoft.archidata.dataAccess.model.TypeManyToManyLocalOIDRoot;
import test.atriasoft.archidata.dataAccess.model.TypeManyToManyLocalOIDRootExpand;
@ExtendWith(StepwiseExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TestManyToManyLocalOID {
final static private Logger LOGGER = LoggerFactory.getLogger(TestManyToManyLocalOID.class);
@BeforeAll
public static void configureWebServer() throws Exception {
ConfigureDb.configure();
}
@AfterAll
public static void removeDataBase() throws IOException {
ConfigureDb.clear();
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class SimpleTestInsertionAndRetrieve {
@BeforeAll
public void testCreateTable() throws Exception {
final List<String> sqlCommand2 = DataFactory.createTable(TypeManyToManyLocalOIDRoot.class);
final List<String> sqlCommand = DataFactory.createTable(TypeManyToManyLocalOIDRemote.class);
sqlCommand.addAll(sqlCommand2);
if (ConfigureDb.da instanceof final DBAccessSQL daSQL) {
for (final String elem : sqlCommand) {
LOGGER.debug("request: '{}'", elem);
daSQL.executeSimpleQuery(elem);
}
}
}
@AfterAll
public void dropTables() throws Exception {
if (ConfigureDb.da instanceof final DBAccessSQL daSQL) {
daSQL.drop(TypeManyToManyLocalOIDRoot.class);
daSQL.drop(TypeManyToManyLocalOIDRemote.class);
}
}
@Order(2)
@Test
public void testSimpleInsertAndRetieve() throws Exception {
final TypeManyToManyLocalOIDRoot test = new TypeManyToManyLocalOIDRoot();
test.otherData = "root insert";
final TypeManyToManyLocalOIDRoot insertedData = ConfigureDb.da.insert(test);
Assertions.assertNotNull(insertedData);
Assertions.assertNotNull(insertedData.oid);
Assertions.assertNull(insertedData.remote);
// Try to retrieve all the data:
final TypeManyToManyLocalOIDRoot retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
insertedData.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(insertedData.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.otherData);
Assertions.assertEquals(insertedData.otherData, retrieve.otherData);
Assertions.assertNull(retrieve.remote);
ConfigureDb.da.delete(TypeManyToManyLocalOIDRoot.class, insertedData.oid);
}
}
// TODO: add and remove link from remote class
@Order(3)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class AddLinkInsertInRoot {
TypeManyToManyLocalOIDRemote insertedRemote1;
TypeManyToManyLocalOIDRemote insertedRemote2;
TypeManyToManyLocalOIDRoot insertedData;
@BeforeAll
public void testCreateTable() throws Exception {
final List<String> sqlCommand2 = DataFactory.createTable(TypeManyToManyLocalOIDRoot.class);
final List<String> sqlCommand = DataFactory.createTable(TypeManyToManyLocalOIDRemote.class);
sqlCommand.addAll(sqlCommand2);
if (ConfigureDb.da instanceof final DBAccessSQL daSQL) {
for (final String elem : sqlCommand) {
LOGGER.debug("request: '{}'", elem);
daSQL.executeSimpleQuery(elem);
}
}
}
@AfterAll
public void dropTables() throws Exception {
if (ConfigureDb.da instanceof final DBAccessSQL daSQL) {
daSQL.drop(TypeManyToManyLocalOIDRoot.class);
daSQL.drop(TypeManyToManyLocalOIDRemote.class);
}
}
// ---------------------------------------------------------------
// -- Add remote:
// ---------------------------------------------------------------
@Order(1)
@Test
public void addRemotes() throws Exception {
TypeManyToManyLocalOIDRemote remote = new TypeManyToManyLocalOIDRemote();
for (int iii = 0; iii < 100; iii++) {
remote.data = "tmp" + iii;
this.insertedRemote1 = ConfigureDb.da.insert(remote);
ConfigureDb.da.delete(TypeManyToManyLocalOIDRemote.class, this.insertedRemote1.oid);
}
remote = new TypeManyToManyLocalOIDRemote();
remote.data = "remote1";
this.insertedRemote1 = ConfigureDb.da.insert(remote);
Assertions.assertEquals(this.insertedRemote1.data, remote.data);
remote = new TypeManyToManyLocalOIDRemote();
remote.data = "remote2";
this.insertedRemote2 = ConfigureDb.da.insert(remote);
Assertions.assertEquals(this.insertedRemote2.data, remote.data);
}
@Order(2)
@Test
public void insertDataWithoutRemote() throws Exception {
final TypeManyToManyLocalOIDRoot test = new TypeManyToManyLocalOIDRoot();
test.otherData = "root insert 55";
this.insertedData = ConfigureDb.da.insert(test);
Assertions.assertNotNull(this.insertedData);
Assertions.assertNotNull(this.insertedData.oid);
Assertions.assertNull(this.insertedData.remote);
// Try to retrieve all the data:
final TypeManyToManyLocalOIDRoot retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedData.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(this.insertedData.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.otherData);
Assertions.assertEquals(this.insertedData.otherData, retrieve.otherData);
Assertions.assertNull(retrieve.remote);
}
@Order(3)
@Test
public void addLinksRemotes() throws Exception {
// Add remote elements
AddOnManyToManyLocal.addLink(ConfigureDb.da, //
TypeManyToManyLocalOIDRoot.class, //
this.insertedData.oid, //
"remote", this.insertedRemote1.oid);
AddOnManyToManyLocal.addLink(ConfigureDb.da, //
TypeManyToManyLocalOIDRoot.class, //
this.insertedData.oid, //
"remote", this.insertedRemote2.oid);
final TypeManyToManyLocalOIDRoot retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedData.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(this.insertedData.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.otherData);
Assertions.assertEquals(this.insertedData.otherData, retrieve.otherData);
Assertions.assertNotNull(retrieve.remote);
Assertions.assertEquals(2, retrieve.remote.size());
Assertions.assertEquals(retrieve.remote.get(0), this.insertedRemote1.oid);
Assertions.assertEquals(retrieve.remote.get(1), this.insertedRemote2.oid);
// -- Verify remote is linked:
final TypeManyToManyLocalOIDRemote retrieveRemote = ConfigureDb.da.get(TypeManyToManyLocalOIDRemote.class,
this.insertedRemote1.oid);
Assertions.assertNotNull(retrieveRemote);
Assertions.assertNotNull(retrieveRemote.oid);
Assertions.assertEquals(this.insertedRemote1.oid, retrieveRemote.oid);
Assertions.assertNotNull(retrieveRemote.data);
Assertions.assertEquals(this.insertedRemote1.data, retrieveRemote.data);
Assertions.assertNotNull(retrieveRemote.remoteToParent);
Assertions.assertEquals(1, retrieveRemote.remoteToParent.size());
Assertions.assertEquals(this.insertedData.oid, retrieveRemote.remoteToParent.get(0));
}
@Order(3)
@Test
public void testExpand() throws Exception {
final TypeManyToManyLocalOIDRootExpand retrieveExpand = ConfigureDb.da
.get(TypeManyToManyLocalOIDRootExpand.class, this.insertedData.oid);
Assertions.assertNotNull(retrieveExpand);
Assertions.assertNotNull(retrieveExpand.oid);
Assertions.assertEquals(this.insertedData.oid, retrieveExpand.oid);
Assertions.assertNotNull(retrieveExpand.otherData);
Assertions.assertEquals(this.insertedData.otherData, retrieveExpand.otherData);
Assertions.assertNotNull(retrieveExpand.remote);
Assertions.assertEquals(2, retrieveExpand.remote.size());
Assertions.assertEquals(retrieveExpand.remote.get(0).oid, this.insertedRemote1.oid);
Assertions.assertEquals(retrieveExpand.remote.get(0).data, this.insertedRemote1.data);
Assertions.assertEquals(retrieveExpand.remote.get(1).oid, this.insertedRemote2.oid);
Assertions.assertEquals(retrieveExpand.remote.get(1).data, this.insertedRemote2.data);
}
@Order(4)
@Test
public void removeLinksRemotes() throws Exception {
// Remove an element
AddOnManyToManyLocal.removeLink(ConfigureDb.da, TypeManyToManyLocalOIDRoot.class, //
this.insertedData.oid, //
"remote", this.insertedRemote1.oid);
TypeManyToManyLocalOIDRoot retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedData.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(this.insertedData.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.otherData);
Assertions.assertEquals(this.insertedData.otherData, retrieve.otherData);
Assertions.assertNotNull(retrieve.remote);
Assertions.assertEquals(retrieve.remote.size(), 1);
Assertions.assertEquals(retrieve.remote.get(0), this.insertedRemote2.oid);
// Remove the second element
AddOnManyToManyLocal.removeLink(ConfigureDb.da, TypeManyToManyLocalOIDRoot.class, //
retrieve.oid, //
"remote", this.insertedRemote2.oid);
retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class, this.insertedData.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(this.insertedData.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.otherData);
Assertions.assertEquals(this.insertedData.otherData, retrieve.otherData);
Assertions.assertNull(retrieve.remote);
ConfigureDb.da.delete(TypeManyToManyLocalOIDRoot.class, this.insertedData.oid);
}
}
@Order(4)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class directInsertAndRemoveInRoot {
TypeManyToManyLocalOIDRemote insertedRemote1;
TypeManyToManyLocalOIDRemote insertedRemote2;
TypeManyToManyLocalOIDRoot insertedRoot1;
TypeManyToManyLocalOIDRoot insertedRoot2;
@BeforeAll
public void testCreateTable() throws Exception {
final List<String> sqlCommand2 = DataFactory.createTable(TypeManyToManyLocalOIDRoot.class);
final List<String> sqlCommand = DataFactory.createTable(TypeManyToManyLocalOIDRemote.class);
sqlCommand.addAll(sqlCommand2);
if (ConfigureDb.da instanceof final DBAccessSQL daSQL) {
for (final String elem : sqlCommand) {
LOGGER.debug("request: '{}'", elem);
daSQL.executeSimpleQuery(elem);
}
}
}
@AfterAll
public void dropTables() throws Exception {
if (ConfigureDb.da instanceof final DBAccessSQL daSQL) {
daSQL.drop(TypeManyToManyLocalOIDRoot.class);
daSQL.drop(TypeManyToManyLocalOIDRemote.class);
}
}
// ---------------------------------------------------------------
// -- Add remote:
// ---------------------------------------------------------------
@Order(1)
@Test
public void addRemotes() throws Exception {
TypeManyToManyLocalOIDRemote remote = new TypeManyToManyLocalOIDRemote();
for (int iii = 0; iii < 100; iii++) {
remote.data = "tmp" + iii;
this.insertedRemote1 = ConfigureDb.da.insert(remote);
ConfigureDb.da.delete(TypeManyToManyLocalOIDRemote.class, this.insertedRemote1.oid);
}
remote = new TypeManyToManyLocalOIDRemote();
remote.data = "remote 1";
this.insertedRemote1 = ConfigureDb.da.insert(remote);
Assertions.assertEquals(this.insertedRemote1.data, remote.data);
remote = new TypeManyToManyLocalOIDRemote();
remote.data = "remote 2";
this.insertedRemote2 = ConfigureDb.da.insert(remote);
Assertions.assertEquals(this.insertedRemote2.data, remote.data);
TypeManyToManyLocalOIDRoot root = new TypeManyToManyLocalOIDRoot();
root.otherData = "root 1";
this.insertedRoot1 = ConfigureDb.da.insert(root);
root = new TypeManyToManyLocalOIDRoot();
root.otherData = "root 2";
this.insertedRoot2 = ConfigureDb.da.insert(root);
}
@Order(3)
@Test
public void addLinksRemotes() throws Exception {
// Add remote elements
AddOnManyToManyLocal.addLink(ConfigureDb.da, TypeManyToManyLocalOIDRemote.class, //
this.insertedRemote2.oid, //
"remoteToParent", this.insertedRoot1.oid);
AddOnManyToManyLocal.addLink(ConfigureDb.da, TypeManyToManyLocalOIDRemote.class, //
this.insertedRemote2.oid, //
"remoteToParent", this.insertedRoot2.oid);
final TypeManyToManyLocalOIDRemote retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRemote.class,
this.insertedRemote2.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(this.insertedRemote2.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.data);
Assertions.assertEquals(this.insertedRemote2.data, retrieve.data);
Assertions.assertNotNull(retrieve.remoteToParent);
Assertions.assertEquals(2, retrieve.remoteToParent.size());
Assertions.assertEquals(this.insertedRoot1.oid, retrieve.remoteToParent.get(0));
Assertions.assertEquals(this.insertedRoot2.oid, retrieve.remoteToParent.get(1));
final TypeManyToManyLocalOIDRoot retrieveExpand = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedRoot1.oid);
Assertions.assertNotNull(retrieveExpand);
Assertions.assertNotNull(retrieveExpand.oid);
Assertions.assertEquals(this.insertedRoot1.oid, retrieveExpand.oid);
Assertions.assertNotNull(retrieveExpand.otherData);
Assertions.assertEquals(this.insertedRoot1.otherData, retrieveExpand.otherData);
Assertions.assertNotNull(retrieveExpand.remote);
Assertions.assertEquals(1, retrieveExpand.remote.size());
Assertions.assertEquals(this.insertedRemote2.oid, retrieveExpand.remote.get(0));
// -- Verify remote is linked:
final TypeManyToManyLocalOIDRoot retrieveRemote = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedRoot2.oid);
Assertions.assertNotNull(retrieveRemote);
Assertions.assertNotNull(retrieveRemote.oid);
Assertions.assertEquals(this.insertedRoot2.oid, retrieveRemote.oid);
Assertions.assertNotNull(retrieveRemote.otherData);
Assertions.assertEquals(this.insertedRoot2.otherData, retrieveRemote.otherData);
Assertions.assertNotNull(retrieveRemote.remote);
Assertions.assertEquals(1, retrieveRemote.remote.size());
Assertions.assertEquals(this.insertedRemote2.oid, retrieveRemote.remote.get(0));
}
@Order(4)
@Test
public void removeLinksRemotes() throws Exception {
// Remove root elements
AddOnManyToManyLocal.removeLink(ConfigureDb.da, TypeManyToManyLocalOIDRemote.class, //
this.insertedRemote2.oid, //
"remoteToParent", this.insertedRoot2.oid);
final TypeManyToManyLocalOIDRemote retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRemote.class,
this.insertedRemote2.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(this.insertedRemote2.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.data);
Assertions.assertEquals(this.insertedRemote2.data, retrieve.data);
Assertions.assertNotNull(retrieve.remoteToParent);
Assertions.assertEquals(1, retrieve.remoteToParent.size());
Assertions.assertEquals(this.insertedRoot1.oid, retrieve.remoteToParent.get(0));
// -- Verify remote is linked:
final TypeManyToManyLocalOIDRoot retrieveExpand = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedRoot1.oid);
Assertions.assertNotNull(retrieveExpand);
Assertions.assertNotNull(retrieveExpand.oid);
Assertions.assertEquals(this.insertedRoot1.oid, retrieveExpand.oid);
Assertions.assertNotNull(retrieveExpand.otherData);
Assertions.assertEquals(this.insertedRoot1.otherData, retrieveExpand.otherData);
Assertions.assertNotNull(retrieveExpand.remote);
Assertions.assertEquals(1, retrieveExpand.remote.size());
Assertions.assertEquals(this.insertedRemote2.oid, retrieveExpand.remote.get(0));
// -- Verify remote is un-linked:
final TypeManyToManyLocalOIDRoot retrieveRemote = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedRoot2.oid);
Assertions.assertNotNull(retrieveRemote);
Assertions.assertNotNull(retrieveRemote.oid);
Assertions.assertEquals(this.insertedRoot2.oid, retrieveRemote.oid);
Assertions.assertNotNull(retrieveRemote.otherData);
Assertions.assertEquals(this.insertedRoot2.otherData, retrieveRemote.otherData);
Assertions.assertNull(retrieveRemote.remote);
}
@Order(5)
@Test
public void removeSecondLinksRemotes() throws Exception {
// Remove root elements
AddOnManyToManyLocal.removeLink(ConfigureDb.da, TypeManyToManyLocalOIDRemote.class, //
this.insertedRemote2.oid, //
"remoteToParent", this.insertedRoot1.oid);
final TypeManyToManyLocalOIDRemote retrieve = ConfigureDb.da.get(TypeManyToManyLocalOIDRemote.class,
this.insertedRemote2.oid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.oid);
Assertions.assertEquals(this.insertedRemote2.oid, retrieve.oid);
Assertions.assertNotNull(retrieve.data);
Assertions.assertEquals(this.insertedRemote2.data, retrieve.data);
Assertions.assertNull(retrieve.remoteToParent);
// -- Verify remote is linked:
final TypeManyToManyLocalOIDRootExpand retrieveExpand = ConfigureDb.da
.get(TypeManyToManyLocalOIDRootExpand.class, this.insertedRoot1.oid);
Assertions.assertNotNull(retrieveExpand);
Assertions.assertNotNull(retrieveExpand.oid);
Assertions.assertEquals(this.insertedRoot1.oid, retrieveExpand.oid);
Assertions.assertNotNull(retrieveExpand.otherData);
Assertions.assertEquals(this.insertedRoot1.otherData, retrieveExpand.otherData);
Assertions.assertNull(retrieveExpand.remote);
// -- Verify remote is un-linked:
final TypeManyToManyLocalOIDRootExpand retrieveRemote = ConfigureDb.da
.get(TypeManyToManyLocalOIDRootExpand.class, this.insertedRoot2.oid);
Assertions.assertNotNull(retrieveRemote);
Assertions.assertNotNull(retrieveRemote.oid);
Assertions.assertEquals(this.insertedRoot2.oid, retrieveRemote.oid);
Assertions.assertNotNull(retrieveRemote.otherData);
Assertions.assertEquals(this.insertedRoot2.otherData, retrieveRemote.otherData);
Assertions.assertNull(retrieveRemote.remote);
}
TypeManyToManyLocalOIDRemote insertedParameters;
// ---------------------------------------------------------------
// -- Add parent with manyToMany in parameters:
// ---------------------------------------------------------------
@Order(6)
@Test
public void AddParentWithManyToManyInParameters() throws Exception {
final TypeManyToManyLocalOIDRemote test = new TypeManyToManyLocalOIDRemote();
test.data = "insert with remote";
test.remoteToParent = new ArrayList<>();
test.remoteToParent.add(this.insertedRoot1.oid);
test.remoteToParent.add(this.insertedRoot2.oid);
this.insertedParameters = ConfigureDb.da.insert(test);
Assertions.assertNotNull(this.insertedParameters);
Assertions.assertNotNull(this.insertedParameters.oid);
Assertions.assertNotNull(this.insertedParameters.remoteToParent);
Assertions.assertEquals(2, this.insertedParameters.remoteToParent.size());
Assertions.assertEquals(this.insertedRoot1.oid, this.insertedParameters.remoteToParent.get(0));
Assertions.assertEquals(this.insertedRoot2.oid, this.insertedParameters.remoteToParent.get(1));
// -- Verify remote is linked:
TypeManyToManyLocalOIDRoot retrieveRoot = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedRoot1.oid);
Assertions.assertNotNull(retrieveRoot);
Assertions.assertNotNull(retrieveRoot.oid);
Assertions.assertEquals(this.insertedRoot1.oid, retrieveRoot.oid);
Assertions.assertNotNull(retrieveRoot.otherData);
Assertions.assertEquals(this.insertedRoot1.otherData, retrieveRoot.otherData);
Assertions.assertNotNull(retrieveRoot.remote);
Assertions.assertEquals(1, retrieveRoot.remote.size());
Assertions.assertEquals(this.insertedParameters.oid, retrieveRoot.remote.get(0));
retrieveRoot = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class, this.insertedRoot2.oid);
Assertions.assertNotNull(retrieveRoot);
Assertions.assertNotNull(retrieveRoot.oid);
Assertions.assertEquals(this.insertedRoot2.oid, retrieveRoot.oid);
Assertions.assertNotNull(retrieveRoot.otherData);
Assertions.assertEquals(this.insertedRoot2.otherData, retrieveRoot.otherData);
Assertions.assertNotNull(retrieveRoot.remote);
Assertions.assertEquals(1, retrieveRoot.remote.size());
Assertions.assertEquals(this.insertedParameters.oid, retrieveRoot.remote.get(0));
}
// ---------------------------------------------------------------
// -- Update Parent Data:
// ---------------------------------------------------------------
@Order(7)
@Test
public void updateRequest() throws Exception {
final TypeManyToManyLocalOIDRemote testUpdate = new TypeManyToManyLocalOIDRemote();
testUpdate.remoteToParent = new ArrayList<>();
testUpdate.remoteToParent.add(this.insertedRoot2.oid);
final long numberUpdate = ConfigureDb.da.update(testUpdate, this.insertedParameters.oid);
Assertions.assertEquals(1, numberUpdate);
final TypeManyToManyLocalOIDRemote insertedDataUpdate = ConfigureDb.da
.get(TypeManyToManyLocalOIDRemote.class, this.insertedParameters.oid);
Assertions.assertNotNull(insertedDataUpdate);
Assertions.assertNotNull(insertedDataUpdate.oid);
Assertions.assertNotNull(insertedDataUpdate.remoteToParent);
Assertions.assertEquals(1, insertedDataUpdate.remoteToParent.size());
Assertions.assertEquals(this.insertedRoot2.oid, insertedDataUpdate.remoteToParent.get(0));
// -- Verify remote is linked (removed):
TypeManyToManyLocalOIDRoot retrieveRoot = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class,
this.insertedRoot1.oid);
Assertions.assertNotNull(retrieveRoot);
Assertions.assertNotNull(retrieveRoot.oid);
Assertions.assertEquals(this.insertedRoot1.oid, retrieveRoot.oid);
Assertions.assertNotNull(retrieveRoot.otherData);
Assertions.assertEquals(this.insertedRoot1.otherData, retrieveRoot.otherData);
Assertions.assertNull(retrieveRoot.remote);
// -- Verify remote is linked (keep):
retrieveRoot = ConfigureDb.da.get(TypeManyToManyLocalOIDRoot.class, this.insertedRoot2.oid);
Assertions.assertNotNull(retrieveRoot);
Assertions.assertNotNull(retrieveRoot.oid);
Assertions.assertEquals(this.insertedRoot2.oid, retrieveRoot.oid);
Assertions.assertNotNull(retrieveRoot.otherData);
Assertions.assertEquals(this.insertedRoot2.otherData, retrieveRoot.otherData);
Assertions.assertNotNull(retrieveRoot.remote);
Assertions.assertEquals(1, retrieveRoot.remote.size());
Assertions.assertEquals(this.insertedParameters.oid, retrieveRoot.remote.get(0));
}
}
}

View File

@ -0,0 +1,17 @@
package test.atriasoft.archidata.dataAccess.model;
import java.util.List;
import org.atriasoft.archidata.annotation.ManyToManyLocal;
import org.atriasoft.archidata.model.OIDGenericData;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
@Entity
public class TypeManyToManyLocalOIDRemote extends OIDGenericData {
@ManyToManyLocal(targetEntity = TypeManyToManyLocalOIDRoot.class, remoteField = "remote")
public List<ObjectId> remoteToParent;
public String data;
}

View File

@ -0,0 +1,18 @@
package test.atriasoft.archidata.dataAccess.model;
import java.util.List;
import org.atriasoft.archidata.annotation.ManyToManyLocal;
import org.atriasoft.archidata.model.OIDGenericData;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
@Entity
public class TypeManyToManyLocalOIDRoot extends OIDGenericData {
public String otherData;
@ManyToManyLocal(targetEntity = TypeManyToManyLocalOIDRemote.class, remoteField = "remoteToParent")
public List<ObjectId> remote;
}

View File

@ -0,0 +1,20 @@
package test.atriasoft.archidata.dataAccess.model;
import java.util.List;
import org.atriasoft.archidata.annotation.ManyToManyLocal;
import org.atriasoft.archidata.model.OIDGenericData;
import dev.morphia.annotations.Entity;
import jakarta.persistence.Table;
@Table(name = "TypeManyToManyLocalOIDRoot")
// for Mongo
@Entity(value = "TypeManyToManyLocalOIDRoot")
public class TypeManyToManyLocalOIDRootExpand extends OIDGenericData {
public String otherData;
@ManyToManyLocal(targetEntity = TypeManyToManyLocalOIDRemote.class, remoteField = "remoteToParent")
public List<TypeManyToManyLocalOIDRemote> remote;
}