Skip to content

Commit 89afc04

Browse files
committed
UIB Data Creation features refactoring aligned
with Dependency resolver in the data module
1 parent f9511a2 commit 89afc04

File tree

12 files changed

+296
-64
lines changed

12 files changed

+296
-64
lines changed

bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public class DependencyResolver {
2525
private static final Set<Class<?>> buildingEntities = new HashSet<>();
2626
private static final Set<Class<?>> creatingEntities = new HashSet<>();
2727
private static final Set<Class<?>> deletingEntities = new HashSet<>();
28-
28+
private static final Set<Class<?>> updatingEntities = new HashSet<>();
29+
2930
/**
3031
* Resolves all dependencies for the given entity by creating
3132
* dependent entities where fields are marked with @Dependency
@@ -56,6 +57,13 @@ public static <T> T createDependencies(T entity) {
5657
return createDependenciesRecursive(entity);
5758
}
5859

60+
public static <T> T updateDependencies(T entity) {
61+
if (entity == null) {
62+
return null;
63+
}
64+
return updateDependenciesRecursive(entity);
65+
}
66+
5967
public static <T> void deleteDependencies(T entity) {
6068
if (entity == null) return;
6169
deletingEntities.clear();
@@ -163,14 +171,46 @@ private static <T> T deleteDependenciesRecursive(T entity) {
163171
deletingEntities.remove(entityClass);
164172
}
165173
}
174+
175+
private static <T> T updateDependenciesRecursive(T entity) {
176+
if (entity == null) {
177+
return null;
178+
}
179+
180+
Class<?> entityClass = entity.getClass();
181+
182+
// Check for circular dependency
183+
if (updatingEntities.contains(entityClass)) {
184+
throw new RuntimeException("Circular dependency detected for entity: " + entityClass.getSimpleName());
185+
}
186+
187+
// Add to resolving set
188+
updatingEntities.add(entityClass);
189+
190+
try {
191+
// Get all fields with @Dependency annotations
192+
List<Field> dependencyFields = getDependencyFields(entityClass);
193+
194+
// Resolve each dependency field
195+
for (Field field : dependencyFields) {
196+
updateFieldDependencyRecursive(entity, field);
197+
}
198+
199+
return entity;
200+
} finally {
201+
// Remove from resolving set
202+
updatingEntities.remove(entityClass);
203+
}
204+
}
205+
166206

167207
/**
168208
* Gets all fields with @Dependency annotations from the given class.
169209
*
170210
* @param entityClass The entity class to scan
171211
* @return List of fields with @Dependency annotations
172212
*/
173-
private static List<Field> getDependencyFields(Class<?> entityClass) {
213+
protected static List<Field> getDependencyFields(Class<?> entityClass) {
174214
List<Field> dependencyFields = new ArrayList<>();
175215
Field[] fields = entityClass.getDeclaredFields();
176216

@@ -333,7 +373,7 @@ private static Object buildDependencyEntity(Dependency dependencyAnnotation) {
333373
* @param entityType The entity class to find a factory for
334374
* @return The factory instance or null if not found
335375
*/
336-
private static EntityFactory<?> findFactoryForEntity(Class<?> entityType) {
376+
public static EntityFactory<?> findFactoryForEntity(Class<?> entityType) {
337377
String entityName = entityType.getSimpleName();
338378
String factoryClassName = entityName + "RepositoryFactory";
339379

@@ -406,4 +446,34 @@ private static boolean trySetViaSetter(Object target, Field field, Object value)
406446
}
407447
return false;
408448
}
449+
450+
private static void updateFieldDependencyRecursive(Object entity, Field field) {
451+
try {
452+
field.setAccessible(true);
453+
Dependency dependencyAnnotation = field.getAnnotation(Dependency.class);
454+
Entity currentValue = (Entity)field.get(entity);
455+
456+
// Skip if field already has a value and forceCreate is false
457+
if (currentValue == null || currentValue.getIdentifier() == null) {
458+
return;
459+
}
460+
461+
// Create the dependency entity using the repository
462+
Object builtDependencyEntity = currentValue == null ? buildDependencyEntity(dependencyAnnotation) : currentValue;
463+
464+
// Get the repository directly using the entity type from the annotation
465+
@SuppressWarnings("unchecked")
466+
Repository<Entity> repository = (Repository<Entity>) RepositoryProvider.INSTANCE.get((Class<? extends Entity>) dependencyAnnotation.entityType());
467+
468+
Log.info("Updating dependency entity for field '" + field.getName() + "' of type '" + dependencyAnnotation.entityType().getSimpleName() + "'.");
469+
repository.update((Entity) builtDependencyEntity);
470+
471+
// Recursively resolve dependencies for the dependency entity
472+
// This ensures that all dependencies are resolved bottom-up
473+
updateDependenciesRecursive(builtDependencyEntity);
474+
475+
} catch (Exception e) {
476+
throw new RuntimeException("Failed to update dependency for field: " + field.getName(), e);
477+
}
478+
}
409479
}

bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/Entity.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
package solutions.bellatrix.data.http.infrastructure;
22

3+
import lombok.NoArgsConstructor;
34
import lombok.experimental.SuperBuilder;
5+
import solutions.bellatrix.data.annotations.Dependency;
46
import solutions.bellatrix.data.configuration.RepositoryProvider;
57
import solutions.bellatrix.data.contracts.Repository;
8+
import solutions.bellatrix.data.http.contracts.EntityFactory;
9+
10+
import java.lang.reflect.Field;
11+
import java.lang.reflect.InvocationTargetException;
12+
import java.util.List;
13+
14+
import static solutions.bellatrix.data.http.infrastructure.DependencyResolver.getDependencyFields;
615

716
@SuperBuilder
17+
@NoArgsConstructor
818
@SuppressWarnings("unchecked")
919
public abstract class Entity<TIdentifier, TEntity> {
1020
public TEntity get() {
@@ -40,6 +50,48 @@ public void deleteDependenciesAndSelf() {
4050
DependencyResolver.deleteDependencies(this);
4151
}
4252

53+
public TEntity getWithDependencies() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
54+
var repository = (Repository<Entity>)RepositoryProvider.INSTANCE.get(this.getClass());
55+
var record = repository.getById(this);
56+
List<Field> dependencyFields = getDependencyFields(record.getClass());
57+
58+
for (Field field : dependencyFields) {
59+
field.setAccessible(true);
60+
Dependency dependencyAnnotation = field.getAnnotation(Dependency.class);
61+
62+
Entity currentValue = (Entity)field.get(this);
63+
// Skip if field already has a value and forceCreate is false
64+
if (currentValue != null && !dependencyAnnotation.forceCreate()) {
65+
break;
66+
}
67+
68+
Field f = record.getClass().getDeclaredField(String.format("id%s", Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1)));
69+
f.setAccessible(true);
70+
var dependencyId = f.get(record).toString();
71+
72+
Class<?> fieldType = field.getType();
73+
EntityFactory<?> factory = DependencyResolver.findFactoryForEntity(fieldType);
74+
if (factory == null) {
75+
throw new IllegalStateException("No factory found for entity type: " + dependencyAnnotation.entityType().getSimpleName());
76+
}
77+
78+
Entity newEntity = factory.buildDefault();
79+
80+
newEntity.setIdentifier(dependencyId);
81+
82+
field.set(record, newEntity.getWithDependencies());
83+
}
84+
85+
return (TEntity) record;
86+
}
87+
88+
public TEntity updateWithDependencies() {
89+
DependencyResolver.updateDependencies(this);
90+
var repository = (Repository<Entity>)RepositoryProvider.INSTANCE.get(this.getClass());
91+
this.setIdentifier((String)repository.update(this).getIdentifier());
92+
return (TEntity)this;
93+
}
94+
4395
public abstract TIdentifier getIdentifier();
4496
public abstract void setIdentifier(String id);
4597
}

bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpEntity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import lombok.Data;
44
import lombok.EqualsAndHashCode;
5+
import lombok.NoArgsConstructor;
56
import lombok.experimental.SuperBuilder;
67
import solutions.bellatrix.data.http.contracts.Queryable;
78

89
import java.util.Objects;
910

1011
@Data
1112
@SuperBuilder
13+
@NoArgsConstructor
1214
@EqualsAndHashCode(callSuper = true)
1315
public abstract class HttpEntity<TIdentifier, TEntity> extends Entity<TIdentifier, TEntity> implements Queryable {
1416
private transient HttpResponse response;

bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/infrastructure/core/entities/ServiceNowEntity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
import com.google.gson.annotations.SerializedName;
44
import lombok.Data;
55
import lombok.EqualsAndHashCode;
6+
import lombok.NoArgsConstructor;
67
import lombok.experimental.SuperBuilder;
78
import solutions.bellatrix.data.http.infrastructure.HttpEntity;
89

910
import java.time.LocalDateTime;
1011

1112
@Data
1213
@SuperBuilder
14+
@NoArgsConstructor
1315
@EqualsAndHashCode(callSuper = true)
1416
public abstract class ServiceNowEntity<TEntity extends ServiceNowEntity> extends HttpEntity<String, TEntity> {
1517
@SerializedName("sys_id")
Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,82 @@
11
package O7_DataCreationHttp;
22

3-
import O4_TableView.data.ProjectTables;
3+
import O7_DataCreationHttp.data.BaseServiceNowDataTest;
4+
import O7_DataCreationHttp.data.ProjectTables;
45
import O7_DataCreationHttp.data.incident.Incident;
56
import O7_DataCreationHttp.data.incident.IncidentRepository;
6-
import O7_DataCreationHttp.data.user.User;
7-
import O7_DataCreationHttp.data.user.UserRepository;
7+
import O7_DataCreationHttp.data.incident.IncidentRepositoryFactory;
8+
import O7_DataCreationHttp.data.user.UserRepositoryFactory;
89
import org.junit.jupiter.api.Assertions;
910
import org.junit.jupiter.api.Test;
10-
import solutions.bellatrix.core.configuration.ConfigurationService;
11-
import solutions.bellatrix.core.utilities.TimestampBuilder;
12-
import solutions.bellatrix.data.http.infrastructure.QueryParameter;
13-
import solutions.bellatrix.servicenow.baseTest.ServiceNowBaseTest;
14-
import solutions.bellatrix.servicenow.infrastructure.configuration.ServiceNowProjectSettings;
1511

12+
import java.lang.reflect.InvocationTargetException;
1613
import java.util.List;
1714

18-
public class DataCreationTests extends ServiceNowBaseTest {
19-
User currentUser;
15+
public class DataCreationTests extends BaseServiceNowDataTest {
16+
protected IncidentRepositoryFactory incidentFactory;
17+
protected UserRepositoryFactory userFactory;
2018

2119
@Override
2220
protected void beforeEach() throws Exception {
2321
super.beforeEach();
24-
UserRepository userRepository = new UserRepository();
25-
currentUser = userRepository.getEntitiesByParameters(List.of(new QueryParameter("user_name", ConfigurationService.get(ServiceNowProjectSettings.class).getUserName()))).get(0);
22+
23+
incidentFactory = new IncidentRepositoryFactory();
24+
userFactory = new UserRepositoryFactory();
25+
26+
registerRepositoriesAndFactories();
2627
}
2728

2829
@Test
29-
public void createEntity() {
30-
IncidentRepository incidentRepository = new IncidentRepository();
30+
public void createEntityWithDependencyDefaultTest() {
31+
// Build incident with dependencies using factory
32+
Incident incident = incidentFactory.buildDefault();
33+
incident.createWithDependencies();
3134

32-
Incident incident = incidentRepository.create(Incident.builder()
33-
.caller(currentUser.getSysId())
34-
.shortDescription(String.format("Incident Report %s", TimestampBuilder.buildUniqueTextByPrefix("au")))
35-
.build());
35+
Assertions.assertNotNull(incident.getSysId());
36+
Assertions.assertNotNull(incident.getCaller().getSysId());
3637

37-
var entityCreated = incidentRepository.getById(incident);
38-
39-
Assertions.assertEquals(incident.getCaller(), entityCreated.getCaller());
38+
incident.deleteDependenciesAndSelf();
4039
}
4140

4241
@Test
43-
public void updateEntity() {
44-
IncidentRepository incidentRepository = new IncidentRepository();
42+
public void createEntityWithDependencyCustomizedTest() throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException {
43+
String expectedIncidentDescription = "Custom Description " + System.currentTimeMillis();
44+
String expectedUserName = "Custom User "+ System.currentTimeMillis();
4545

46-
Incident incident = incidentRepository.create(Incident.builder()
47-
.caller(currentUser.getSysId())
48-
.shortDescription(String.format("Incident Report %s", TimestampBuilder.buildUniqueTextByPrefix("au")))
49-
.build());
46+
// Build incident with dependencies using factory
47+
Incident incident = incidentFactory.buildDefaultWithDependencies();
5048

49+
incident.setShortDescription(expectedIncidentDescription);
50+
incident.getCaller().setUserName(expectedUserName);
5151

52-
var updatedIncident = Incident.builder().sysId(incident.getSysId()).shortDescription("Description updated").build();
53-
incidentRepository.update(updatedIncident);
52+
incident.createWithDependencies();
5453

55-
var entityFromAPI = incidentRepository.getById(incident);
54+
Incident defaultIncident = incidentFactory.buildDefault();
55+
defaultIncident.setIdentifier(incident.getSysId());
5656

57-
Assertions.assertEquals(updatedIncident.getShortDescription(), entityFromAPI.getShortDescription());
58-
}
57+
var entityFromAPI = defaultIncident.getWithDependencies();
5958

60-
@Test
61-
public void deleteEntity() {
62-
IncidentRepository incidentRepository = new IncidentRepository();
63-
64-
Incident incident = incidentRepository.create(Incident.builder()
65-
.caller(currentUser.getSysId())
66-
.shortDescription(String.format("Incident Report %s", TimestampBuilder.buildUniqueTextByPrefix("au")))
67-
.build());
59+
Assertions.assertEquals(entityFromAPI.getShortDescription(), expectedIncidentDescription);
60+
Assertions.assertEquals(entityFromAPI.getCaller().getUserName(), expectedUserName);
6861

69-
incidentRepository.delete(incident);
70-
71-
incidentRepository.validateEntityDoesNotExist(incident);
62+
incident.deleteDependenciesAndSelf();
7263
}
7364

7465
@Test
75-
public void getEntity() {
66+
public void deleteEntity() throws NoSuchFieldException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
67+
// Build incident with dependencies using factory
7668
IncidentRepository incidentRepository = new IncidentRepository();
7769

78-
Incident incident = incidentRepository.create(Incident.builder()
79-
.caller(currentUser.getSysId())
80-
.shortDescription(String.format("Incident Report %s", TimestampBuilder.buildUniqueTextByPrefix("au")))
81-
.build());
70+
Incident incident = incidentFactory.buildDefault();
71+
incident.createWithDependencies();
72+
73+
List<Incident> incidents = incidentRepository.getAll().stream().filter(i -> i.getSysId().equals(incident.getSysId())).toList();
74+
Assertions.assertTrue(incidents.size()==1);
8275

83-
var incidentRegistered = incidentRepository.getById(incident);
76+
incident.deleteDependenciesAndSelf();
8477

85-
Assertions.assertEquals(incident.getShortDescription(), incidentRegistered.getShortDescription());
78+
incidents = incidentRepository.getAll().stream().filter(i -> i.getSysId().equals(incident.getSysId())).toList();
79+
Assertions.assertFalse(incidents.size()==1);
8680
}
8781

8882
@Test
@@ -95,4 +89,29 @@ public void getAllEntities() {
9589

9690
Assertions.assertEquals(serviceNowTableViewPage.map().totalRowsInfo().getText(), String.valueOf(incidents.size()));
9791
}
92+
93+
@Test
94+
public void updateEntityWithDependencyCustomizedTest() throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException {
95+
// Build incident with dependencies using factory
96+
Incident incident = incidentFactory.buildDefaultWithDependencies();
97+
incident.createWithDependencies();
98+
99+
String expectedIncidentDescription = "Custom Description " + System.currentTimeMillis();
100+
String expectedUserName = "Custom User "+ System.currentTimeMillis();
101+
102+
incident.setShortDescription(expectedIncidentDescription);
103+
incident.getCaller().setUserName(expectedUserName);
104+
105+
incident.updateWithDependencies();
106+
107+
Incident defaultIncident = incidentFactory.buildDefault();
108+
defaultIncident.setIdentifier(incident.getSysId());
109+
110+
var entityFromAPI = defaultIncident.getWithDependencies();
111+
112+
Assertions.assertEquals(entityFromAPI.getShortDescription(), expectedIncidentDescription);
113+
Assertions.assertEquals(entityFromAPI.getCaller().getUserName(), expectedUserName);
114+
115+
incident.deleteDependenciesAndSelf();
116+
}
98117
}

0 commit comments

Comments
 (0)