From 4d015f46d9af1fb64b1a99dc91a7c3e7306ef34e Mon Sep 17 00:00:00 2001 From: NikolayAvramov Date: Thu, 25 Sep 2025 13:12:47 +0300 Subject: [PATCH 1/3] Add Dependency resolver into the data module --- .../data/annotations/Dependency.java | 35 ++ .../data/http/contracts/EntityFactory.java | 14 + .../infrastructure/DependencyResolver.java | 209 ++++++++++++ .../bellatrix/data/plugins/README.md | 189 +++++++++++ .../data/plugins/TestDataCleanupPlugin.java | 317 ++++++++++++++++++ 5 files changed, 764 insertions(+) create mode 100644 bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java create mode 100644 bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java create mode 100644 bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java create mode 100644 bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/README.md create mode 100644 bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java new file mode 100644 index 00000000..74e0e595 --- /dev/null +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java @@ -0,0 +1,35 @@ +package solutions.bellatrix.data.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark fields that require automatic dependency creation. + * When a field is marked with this annotation and its value is null, + * the system will automatically create the dependent entity using + * the registered factory and repository. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Dependency { + + /** + * The entity class that should be created as a dependency. + * This class must have a registered factory and repository. + */ + Class entityType(); + + /** + * Optional: Custom factory method name to use for creating the dependency. + * If not specified, the default factory method will be used. + */ + String factoryMethod() default "createDefault"; + + /** + * Optional: Whether to create the dependency even if the field is not null. + * Default is false (only create if field is null). + */ + boolean forceCreate() default false; +} diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java new file mode 100644 index 00000000..cf732b0c --- /dev/null +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java @@ -0,0 +1,14 @@ +package solutions.bellatrix.data.http.contracts; + +import solutions.bellatrix.data.http.infrastructure.DependencyResolver; +import solutions.bellatrix.data.http.infrastructure.Entity; + +public interface EntityFactory { + + T buildDefault(); + + default T createWithDependencies() { + T entity = buildDefault(); + return DependencyResolver.resolveDependencies(entity); + } +} \ No newline at end of file diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java new file mode 100644 index 00000000..fcde85f9 --- /dev/null +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java @@ -0,0 +1,209 @@ +package solutions.bellatrix.data.http.infrastructure; + +import solutions.bellatrix.data.annotations.Dependency; +import solutions.bellatrix.data.configuration.RepositoryFactory; +import solutions.bellatrix.data.contracts.Repository; +import solutions.bellatrix.data.http.contracts.EntityFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; + +/** + * Utility class for automatically resolving entity dependencies. + * This class handles the creation of dependent entities when fields + * are marked with @Dependency annotations using recursive resolution + * to handle complex dependency chains. + */ +public class DependencyResolver { + + // Track entities being resolved to prevent circular dependencies + private static final Set> resolvingEntities = new HashSet<>(); + + /** + * Resolves all dependencies for the given entity by creating + * dependent entities where fields are marked with @Dependency + * and the field value is null. Uses recursive resolution to handle + * complex dependency chains. + * + * @param entity The entity to resolve dependencies for + * @param The entity type + * @return The entity with all dependencies resolved + */ + public static T resolveDependencies(T entity) { + if (entity == null) { + return null; + } + + // Clear the resolving set for each top-level call + resolvingEntities.clear(); + return resolveDependenciesRecursive(entity); + } + + /** + * Recursively resolves dependencies for an entity, ensuring that + * dependencies are created in the correct order (bottom-up). + * + * @param entity The entity to resolve dependencies for + * @param The entity type + * @return The entity with all dependencies resolved + */ + private static T resolveDependenciesRecursive(T entity) { + if (entity == null) { + return null; + } + + Class entityClass = entity.getClass(); + + // Check for circular dependency + if (resolvingEntities.contains(entityClass)) { + throw new RuntimeException("Circular dependency detected for entity: " + entityClass.getSimpleName()); + } + + // Add to resolving set + resolvingEntities.add(entityClass); + + try { + // Get all fields with @Dependency annotations + List dependencyFields = getDependencyFields(entityClass); + + // Resolve each dependency field + for (Field field : dependencyFields) { + resolveFieldDependencyRecursive(entity, field); + } + + return entity; + } finally { + // Remove from resolving set + resolvingEntities.remove(entityClass); + } + } + + /** + * Gets all fields with @Dependency annotations from the given class. + * + * @param entityClass The entity class to scan + * @return List of fields with @Dependency annotations + */ + private static List getDependencyFields(Class entityClass) { + List dependencyFields = new ArrayList<>(); + Field[] fields = entityClass.getDeclaredFields(); + + for (Field field : fields) { + if (field.getAnnotation(Dependency.class) != null) { + dependencyFields.add(field); + } + } + + return dependencyFields; + } + + /** + * Recursively resolves a single field dependency by creating the required entity + * and setting its ID in the field. This method ensures that the dependency entity + * is fully resolved (including its own dependencies) before being created. + * + * @param entity The parent entity + * @param field The field that requires dependency resolution + */ + private static void resolveFieldDependencyRecursive(Object entity, Field field) { + try { + field.setAccessible(true); + Dependency dependencyAnnotation = field.getAnnotation(Dependency.class); + Object currentValue = field.get(entity); + + // Skip if field already has a value and forceCreate is false + if (currentValue != null && !dependencyAnnotation.forceCreate()) { + return; + } + + // Create the dependency entity using the factory + Object dependencyEntity = createDependencyEntity(dependencyAnnotation); + + // Recursively resolve dependencies for the dependency entity + // This ensures that all dependencies are resolved bottom-up + dependencyEntity = resolveDependenciesRecursive(dependencyEntity); + + // Get the repository directly using the entity type from the annotation + @SuppressWarnings("unchecked") + Repository repository = (Repository) RepositoryFactory.INSTANCE.getRepository((Class) dependencyAnnotation.entityType()); + + // Create the fully resolved entity in the repository (this will set the ID) + Entity createdEntity = repository.create((Entity) dependencyEntity); + + // Extract the ID from the created entity + String entityId = extractEntityId(createdEntity); + + // Set the ID in the field + field.set(entity, entityId); + + } catch (Exception e) { + throw new RuntimeException("Failed to resolve dependency for field: " + field.getName(), e); + } + } + + /** + * Creates a dependency entity using the specified factory method. + * + * @param dependencyAnnotation The dependency annotation containing creation info + * @return The created dependency entity + */ + private static Object createDependencyEntity(Dependency dependencyAnnotation) { + try { + // Find the factory for the entity type + EntityFactory factory = findFactoryForEntity(dependencyAnnotation.entityType()); + if (factory == null) { + throw new IllegalStateException("No factory found for entity type: " + dependencyAnnotation.entityType().getSimpleName()); + } + + // Use the specified factory method or default + String methodName = dependencyAnnotation.factoryMethod(); + Method factoryMethod = factory.getClass().getMethod(methodName); + + return factoryMethod.invoke(factory); + + } catch (Exception e) { + throw new RuntimeException("Failed to create dependency entity", e); + } + } + + /** + * Finds the factory for the given entity type by looking for + * factory classes that implement EntityFactory. + * + * @param entityType The entity class to find a factory for + * @return The factory instance or null if not found + */ + private static EntityFactory findFactoryForEntity(Class entityType) { + String entityName = entityType.getSimpleName(); + String factoryClassName = entityName + "RepositoryFactory"; + + try { + // Try to find the factory in the same package as the entity + String packageName = entityType.getPackage().getName(); + Class factoryClass = Class.forName(packageName + "." + factoryClassName); + + // For static classes, create a temporary instance to call methods + return (EntityFactory) factoryClass.getDeclaredConstructor().newInstance(); + + } catch (Exception e) { + throw new RuntimeException("Factory not found for entity type: " + entityType.getSimpleName(), e); + } + } + + /** + * Extracts the ID from an entity object by calling the getId() method. + * + * @param entity The entity to extract ID from + * @return The entity ID as a string + */ + private static String extractEntityId(Object entity) { + try { + Method getIdMethod = entity.getClass().getMethod("getId"); + Object id = getIdMethod.invoke(entity); + return id != null ? id.toString() : null; + } catch (Exception e) { + throw new RuntimeException("Failed to extract ID from entity: " + entity.getClass().getSimpleName(), e); + } + } +} \ No newline at end of file diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/README.md b/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/README.md new file mode 100644 index 00000000..32752f66 --- /dev/null +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/README.md @@ -0,0 +1,189 @@ +# TestDataCleanupPlugin + +The `TestDataCleanupPlugin` is a powerful plugin for the Bellatrix framework that automatically tracks and cleans up test data created during test execution. This plugin helps prevent test data pollution by ensuring that entities created during tests are properly cleaned up after test completion. + +## Features + +- **Automatic Entity Tracking**: Monitors `HttpRepository` events to track entity creation +- **Intelligent Cleanup**: Automatically deletes tracked entities after test completion +- **Thread-Safe**: Uses thread-local storage for proper isolation between parallel tests +- **Performance Optimized**: Caches repository instances and uses efficient data structures +- **Configurable**: Supports enabling/disabling cleanup and logging +- **Error Handling**: Gracefully handles cleanup failures with detailed logging +- **Dependency Aware**: Cleans up entities in reverse order to handle dependencies + +## How It Works + +1. **Event Listening**: The plugin listens to `HttpRepository.ENTITY_CREATED` and `HttpRepository.ENTITY_DELETED` events +2. **Entity Tracking**: When entities are created, they are added to a thread-local tracking queue +3. **Cleanup Prevention**: If entities are manually deleted, they are removed from tracking +4. **Automatic Cleanup**: After test completion, all tracked entities are deleted in reverse order +5. **Repository Resolution**: Uses `RepositoryFactory` to get the appropriate repository for each entity type + +## Usage + +### Basic Usage + +```java +public class MyTest extends BaseTest { + @Override + protected void configure() { + // Add the plugin with default settings (enabled=true, logging=true) + addPlugin(TestDataCleanupPlugin.class); + } + + public void testWithEntityCreation() { + // Create entities - they will be automatically tracked + User user = new User().name("Test User").email("test@example.com").create(); + Order order = new Order().userId(user.getId()).amount(100.0).create(); + + // Test logic here... + + // Entities will be automatically cleaned up after test completion + } +} +``` + +### Advanced Configuration + +```java +public class MyTest extends BaseTest { + @Override + protected void configure() { + // Custom configuration: enabled=true, logging=false + addPlugin(TestDataCleanupPlugin.class, true, false); + } +} +``` + +### Disabling Cleanup + +```java +public class MyTest extends BaseTest { + @Override + protected void configure() { + // Disable cleanup (useful for debugging) + addPlugin(TestDataCleanupPlugin.class, false, true); + } +} +``` + +## Configuration Options + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `isEnabled` | boolean | true | Whether the plugin should perform cleanup | +| `enableLogging` | boolean | true | Whether to log cleanup operations | + +## Event Integration + +The plugin integrates with the following `HttpRepository` events: + +- `ENTITY_CREATED`: Tracks newly created entities +- `ENTITY_DELETED`: Removes manually deleted entities from tracking + +## Lifecycle Integration + +The plugin hooks into the following test lifecycle events: + +- `preBeforeTest`: Clears previous entity tracking +- `postAfterTest`: Performs cleanup of tracked entities +- `postAfterClass`: Clears any remaining tracked entities + +## Thread Safety + +The plugin is designed to be thread-safe and works correctly with parallel test execution: + +- Uses `ThreadLocal` storage for entity tracking +- Uses `ConcurrentHashMap` for repository caching +- Uses `ConcurrentLinkedQueue` for entity tracking + +## Error Handling + +The plugin includes comprehensive error handling: + +- Continues cleanup even if individual entities fail to delete +- Logs detailed error information for failed cleanup operations +- Provides summary statistics of successful and failed cleanups + +## Performance Considerations + +- **Repository Caching**: Repository instances are cached to avoid repeated lookups +- **Efficient Data Structures**: Uses `ConcurrentLinkedQueue` for O(1) insertion and removal +- **Minimal Overhead**: Event listeners are lightweight and only track necessary information +- **Optional Logging**: Logging can be disabled for maximum performance + +## Best Practices + +1. **Enable Logging in Development**: Use `enableLogging=true` during development to monitor cleanup operations +2. **Disable Logging in CI/CD**: Use `enableLogging=false` in production environments for better performance +3. **Handle Dependencies**: The plugin cleans up entities in reverse order, but consider entity relationships when designing tests +4. **Test Isolation**: Each test gets its own tracking queue, ensuring proper isolation +5. **Manual Cleanup**: If you manually delete entities in your tests, they will be automatically removed from tracking + +## Troubleshooting + +### Entities Not Being Cleaned Up + +- Ensure the plugin is properly configured in your test class +- Check that entities are being created through `HttpRepository` (not directly) +- Verify that entity classes are properly registered with `RepositoryFactory` + +### Cleanup Failures + +- Check the logs for detailed error messages +- Ensure the test environment has proper permissions for delete operations +- Verify that entity identifiers are valid and entities exist + +### Performance Issues + +- Disable logging if not needed: `addPlugin(TestDataCleanupPlugin.class, true, false)` +- Consider the number of entities being created per test +- Monitor repository caching effectiveness + +## Example Test Scenarios + +### Simple Entity Creation +```java +public void testCreateUser() { + User user = new User().name("Test User").create(); + // User will be automatically cleaned up +} +``` + +### Multiple Entity Creation +```java +public void testCreateUserWithOrders() { + User user = new User().name("Test User").create(); + Order order1 = new Order().userId(user.getId()).create(); + Order order2 = new Order().userId(user.getId()).create(); + // All entities will be cleaned up in reverse order +} +``` + +### Mixed Creation and Deletion +```java +public void testCreateAndDeleteSomeEntities() { + User user = new User().name("Test User").create(); + Order order = new Order().userId(user.getId()).create(); + + order.delete(); // Removed from tracking + + // Only user will be cleaned up +} +``` + +## Integration with Other Plugins + +The `TestDataCleanupPlugin` works well with other Bellatrix plugins: + +- **ScreenshotPlugin**: Cleanup happens after screenshots are taken +- **VideoPlugin**: Cleanup happens after video recording +- **Logging Plugins**: Cleanup operations are logged if enabled + +## Limitations + +1. **HttpRepository Only**: Currently only works with `HttpRepository` implementations +2. **Entity Dependencies**: Complex entity relationships may require manual cleanup ordering +3. **Cross-Test Dependencies**: Entities created in one test are not cleaned up by other tests +4. **Repository Registration**: All entity types must be properly registered with `RepositoryFactory` diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java new file mode 100644 index 00000000..c885c178 --- /dev/null +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java @@ -0,0 +1,317 @@ +/* + * Copyright 2022 Automate The Planet Ltd. + * Author: Anton Angelov + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package solutions.bellatrix.data.plugins; + +import lombok.extern.slf4j.Slf4j; +import solutions.bellatrix.core.plugins.Plugin; +import solutions.bellatrix.core.plugins.TestResult; +import solutions.bellatrix.core.plugins.TimeRecord; +import solutions.bellatrix.core.utilities.Log; +import solutions.bellatrix.data.configuration.RepositoryFactory; +import solutions.bellatrix.data.contracts.Repository; +import solutions.bellatrix.data.http.infrastructure.Entity; +import solutions.bellatrix.data.http.infrastructure.HttpEntity; +import solutions.bellatrix.data.http.infrastructure.HttpRepository; +import solutions.bellatrix.data.http.infrastructure.events.EntityCreatedEventArgs; +import solutions.bellatrix.data.http.infrastructure.events.EntityDeletedEventArgs; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Plugin that automatically tracks entities created during test execution + * and cleans them up after test completion to prevent test data pollution. + * + * This plugin listens to HttpRepository events to track entity creation + * and automatically deletes tracked entities after test completion. + */ +@Slf4j +public class TestDataCleanupPlugin extends Plugin { + + /** + * Thread-local storage for entities created during the current test. + * Uses a queue to maintain creation order for proper cleanup. + */ + private static final ThreadLocal> CREATED_ENTITIES = + ThreadLocal.withInitial(ConcurrentLinkedQueue::new); + + /** + * Cache of repository instances to avoid repeated lookups. + */ + private static final Map>, Repository> REPOSITORY_CACHE = + new ConcurrentHashMap<>(); + + /** + * Flag to control whether cleanup should be performed. + * Can be disabled for debugging or specific test scenarios. + */ + private final boolean isEnabled; + + /** + * Flag to control whether to log cleanup operations. + */ + private final boolean enableLogging; + + /** + * Creates a new TestDataCleanupPlugin with default settings. + */ + public TestDataCleanupPlugin() { + this(true, true); + } + + /** + * Creates a new TestDataCleanupPlugin with custom settings. + * + * @param isEnabled Whether the plugin should perform cleanup + * @param enableLogging Whether to log cleanup operations + */ + public TestDataCleanupPlugin(boolean isEnabled, boolean enableLogging) { + this.isEnabled = isEnabled; + this.enableLogging = enableLogging; + setupEventListeners(); + } + + /** + * Sets up event listeners for entity creation and deletion events. + */ + private void setupEventListeners() { + // Listen for entity creation events + HttpRepository.ENTITY_CREATED.addListener(this::onEntityCreated); + + // Listen for entity deletion events to remove from tracking + HttpRepository.ENTITY_DELETED.addListener(this::onEntityDeleted); + } + + /** + * Called when an entity is created. Adds the entity to the tracking list. + * + * @param eventArgs The entity creation event arguments + */ + private void onEntityCreated(EntityCreatedEventArgs eventArgs) { + if (!isEnabled) { + return; + } + + Entity entity = eventArgs.getEntity(); + if (entity != null && !(entity instanceof HttpEntity httpEntity && httpEntity.hasInvalidIdentifier())) { + EntityInfo entityInfo = new EntityInfo(entity, System.currentTimeMillis()); + CREATED_ENTITIES.get().offer(entityInfo); + + if (enableLogging) { + Log.info("TestDataCleanupPlugin: Tracking entity for cleanup - Type: %s, ID: %s", + entity.getClass().getSimpleName(), entity.getIdentifier()); + } + } + } + + /** + * Called when an entity is deleted. Removes the entity from tracking. + * + * @param eventArgs The entity deletion event arguments + */ + private void onEntityDeleted(EntityDeletedEventArgs eventArgs) { + if (!isEnabled) { + return; + } + + Entity deletedEntity = eventArgs.getEntity(); + if (deletedEntity != null) { + // Remove the entity from tracking since it's already deleted + CREATED_ENTITIES.get().removeIf(entityInfo -> + entityInfo.getEntity().equals(deletedEntity)); + + if (enableLogging) { + Log.info("TestDataCleanupPlugin: Entity already deleted, removing from tracking - Type: %s, ID: %s", + deletedEntity.getClass().getSimpleName(), deletedEntity.getIdentifier()); + } + } + } + + /** + * Called before each test starts. Clears any previously tracked entities. + * + * @param testResult The test result + * @param memberInfo The test method information + */ + @Override + public void preBeforeTest(TestResult testResult, Method memberInfo) { + if (!isEnabled) { + return; + } + + // Clear any previously tracked entities for this test + CREATED_ENTITIES.get().clear(); + + if (enableLogging) { + Log.info("TestDataCleanupPlugin: Starting test '%s' - cleared previous entity tracking", + memberInfo.getName()); + } + } + + /** + * Called after each test completes. Performs cleanup of tracked entities. + * + * @param testResult The test result + * @param timeRecord The test execution time record + * @param memberInfo The test method information + * @param failedTestException Any exception that caused test failure + */ + @Override + public void postAfterTest(TestResult testResult, TimeRecord timeRecord, Method memberInfo, Throwable failedTestException) { + if (!isEnabled) { + return; + } + + cleanupTrackedEntities(memberInfo.getName()); + } + + /** + * Performs cleanup of all tracked entities for the current test. + * + * @param testName The name of the test being cleaned up + */ + private void cleanupTrackedEntities(String testName) { + Queue entitiesToCleanup = CREATED_ENTITIES.get(); + + if (entitiesToCleanup.isEmpty()) { + if (enableLogging) { + Log.info("TestDataCleanupPlugin: No entities to cleanup for test '%s'", testName); + } + return; + } + + if (enableLogging) { + Log.info("TestDataCleanupPlugin: Starting cleanup for test '%s' - %d entities to cleanup", + testName, entitiesToCleanup.size()); + } + + int successCount = 0; + int failureCount = 0; + + // Process entities in reverse order (LIFO) to handle dependencies + List entitiesList = new ArrayList<>(entitiesToCleanup); + Collections.reverse(entitiesList); + + for (EntityInfo entityInfo : entitiesList) { + try { + cleanupEntity(entityInfo); + successCount++; + } catch (Exception e) { + failureCount++; + Log.error("TestDataCleanupPlugin: Failed to cleanup entity - Type: %s, ID: %s, Error: %s", + entityInfo.getEntity().getClass().getSimpleName(), + entityInfo.getEntity().getIdentifier(), + e.getMessage()); + } + } + + // Clear the tracking list + entitiesToCleanup.clear(); + + if (enableLogging) { + Log.info("TestDataCleanupPlugin: Cleanup completed for test '%s' - Success: %d, Failures: %d", + testName, successCount, failureCount); + } + } + + /** + * Performs cleanup of a single entity. + * + * @param entityInfo The entity information to cleanup + */ + @SuppressWarnings("unchecked") + private void cleanupEntity(EntityInfo entityInfo) { + Entity entity = entityInfo.getEntity(); + Class> entityClass = (Class>) entity.getClass(); + + try { + // Get or create repository instance + Repository> repository = (Repository>) getRepository(entityClass); + + if (enableLogging) { + Log.info("TestDataCleanupPlugin: Deleting entity - Type: %s, ID: %s", + entityClass.getSimpleName(), entity.getIdentifier()); + } + + // Delete the entity + repository.delete(entity); + + } catch (Exception e) { + Log.error("TestDataCleanupPlugin: Error during entity cleanup - Type: %s, ID: %s, Error: %s", + entityClass.getSimpleName(), entity.getIdentifier(), e.getMessage()); + throw e; + } + } + + /** + * Gets a repository instance for the given entity class, using caching for performance. + * + * @param entityClass The entity class + * @return The repository instance + */ + @SuppressWarnings("unchecked") + private Repository getRepository(Class> entityClass) { + return REPOSITORY_CACHE.computeIfAbsent(entityClass, + clazz -> RepositoryFactory.INSTANCE.getRepository(clazz)); + } + + /** + * Called after all tests in a class complete. Clears any remaining tracked entities. + * + * @param type The test class + */ + @Override + public void postAfterClass(Class type) { + if (!isEnabled) { + return; + } + + // Clear any remaining tracked entities + CREATED_ENTITIES.get().clear(); + + if (enableLogging) { + Log.info("TestDataCleanupPlugin: Test class completed - cleared all entity tracking"); + } + } + + /** + * Inner class to hold entity information along with creation timestamp. + */ + private static class EntityInfo { + private final Entity entity; + + public EntityInfo(Entity entity, long createdTimestamp) { + this.entity = entity; + } + + public Entity getEntity() { + return entity; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + EntityInfo that = (EntityInfo) obj; + return Objects.equals(entity, that.entity); + } + + @Override + public int hashCode() { + return Objects.hash(entity); + } + } +} From b31b87d2261db465328fe1e2709db262d6df15f6 Mon Sep 17 00:00:00 2001 From: NikolayAvramov Date: Fri, 26 Sep 2025 00:08:58 +0300 Subject: [PATCH 2/3] fix build issues and add dependency resolving in data module --- .../configuration/ConfigurationService.java | 7 +- .../data/annotations/Dependency.java | 2 +- .../data/configuration/FactoryProvider.java | 29 ++ ...ryFactory.java => RepositoryProvider.java} | 6 +- .../data/http/contracts/EntityFactory.java | 4 +- .../infrastructure/DependencyResolver.java | 262 +++++++++++++++--- .../data/http/infrastructure/Entity.java | 24 +- .../http/infrastructure/HttpRepository.java | 5 +- .../data/plugins/TestDataCleanupPlugin.java | 6 +- bellatrix.playwright/pom.xml | 15 - bellatrix.plugins.opencv/pom.xml | 6 - .../core/entities/ServiceNowEntity.java | 5 + .../converters/FxCurrencyConverter.java | 13 +- .../setupData/FxCurrency2Instance.java | 10 + .../java/infrastructure/artist/Artist.java | 5 + .../src/main/java/Artist.java | 5 + .../src/test/java/BaseTest.java | 4 +- 17 files changed, 328 insertions(+), 80 deletions(-) create mode 100644 bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/FactoryProvider.java rename bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/{RepositoryFactory.java => RepositoryProvider.java} (78%) diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java index 4fcd1c03..95d807ff 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java @@ -14,6 +14,7 @@ package solutions.bellatrix.core.configuration; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonParser; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @@ -57,7 +58,11 @@ public static T get(Class configSection) { String jsonFileContent = getFileAsString(fileName); String sectionName = getSectionName(configSection); - var jsonObject = JsonParser.parseString(jsonFileContent).getAsJsonObject().get(sectionName).toString(); + JsonElement sectionFound = JsonParser.parseString(jsonFileContent).getAsJsonObject().get(sectionName); + if (sectionFound == null) { + return mappedObject; + } + var jsonObject = sectionFound.toString(); var gson = new Gson(); diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java index 74e0e595..e66bf689 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/annotations/Dependency.java @@ -25,7 +25,7 @@ * Optional: Custom factory method name to use for creating the dependency. * If not specified, the default factory method will be used. */ - String factoryMethod() default "createDefault"; + String factoryMethod() default "buildDefault"; /** * Optional: Whether to create the dependency even if the field is not null. diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/FactoryProvider.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/FactoryProvider.java new file mode 100644 index 00000000..a72739ec --- /dev/null +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/FactoryProvider.java @@ -0,0 +1,29 @@ +package solutions.bellatrix.data.configuration; + +import solutions.bellatrix.core.utilities.SingletonFactory; +import solutions.bellatrix.data.http.contracts.EntityFactory; +import solutions.bellatrix.data.http.infrastructure.Entity; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public enum FactoryProvider { + INSTANCE; + + private final Map, Class> factories = new ConcurrentHashMap<>(); + + public void register(Class entityClass, Class> factoryClass) { + factories.put(entityClass, factoryClass); + } + + public EntityFactory get(Class entityClass) { + var factoryClassType = factories.get(entityClass); + + if (Objects.isNull(factoryClassType)) { + throw new IllegalArgumentException("No factory registered for entity class: " + entityClass.getName()); + } + + return (EntityFactory)SingletonFactory.getInstance(factoryClassType); + } +} \ No newline at end of file diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/RepositoryFactory.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/RepositoryProvider.java similarity index 78% rename from bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/RepositoryFactory.java rename to bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/RepositoryProvider.java index 0fe9e32a..ad70b448 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/RepositoryFactory.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/configuration/RepositoryProvider.java @@ -8,16 +8,16 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -public enum RepositoryFactory { +public enum RepositoryProvider { INSTANCE; private final Map, Class> repositories = new ConcurrentHashMap<>(); - public void registerRepository(Class entityClass, Class> repositoryClass) { + public void register(Class entityClass, Class> repositoryClass) { repositories.put(entityClass, repositoryClass); } - public Repository getRepository(Class entityClass) { + public Repository get(Class entityClass) { var repositoryClassType = repositories.get(entityClass); if (Objects.isNull(repositoryClassType)) { diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java index cf732b0c..0a6cfd3f 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/contracts/EntityFactory.java @@ -7,8 +7,8 @@ public interface EntityFactory { T buildDefault(); - default T createWithDependencies() { + default T buildDefaultWithDependencies() { T entity = buildDefault(); - return DependencyResolver.resolveDependencies(entity); + return DependencyResolver.buildDependencies(entity); } } \ No newline at end of file diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java index fcde85f9..013389ca 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/DependencyResolver.java @@ -1,13 +1,17 @@ package solutions.bellatrix.data.http.infrastructure; +import solutions.bellatrix.core.utilities.Log; import solutions.bellatrix.data.annotations.Dependency; -import solutions.bellatrix.data.configuration.RepositoryFactory; +import solutions.bellatrix.data.configuration.RepositoryProvider; import solutions.bellatrix.data.contracts.Repository; import solutions.bellatrix.data.http.contracts.EntityFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Utility class for automatically resolving entity dependencies. @@ -18,7 +22,9 @@ public class DependencyResolver { // Track entities being resolved to prevent circular dependencies - private static final Set> resolvingEntities = new HashSet<>(); + private static final Set> buildingEntities = new HashSet<>(); + private static final Set> creatingEntities = new HashSet<>(); + private static final Set> deletingEntities = new HashSet<>(); /** * Resolves all dependencies for the given entity by creating @@ -30,16 +36,32 @@ public class DependencyResolver { * @param The entity type * @return The entity with all dependencies resolved */ - public static T resolveDependencies(T entity) { + public static T buildDependencies(T entity) { if (entity == null) { return null; } // Clear the resolving set for each top-level call - resolvingEntities.clear(); - return resolveDependenciesRecursive(entity); + buildingEntities.clear(); + return buildDependenciesRecursive(entity); } - + + public static T createDependencies(T entity) { + if (entity == null) { + return null; + } + + // Clear the resolving set for each top-level call + creatingEntities.clear(); + return createDependenciesRecursive(entity); + } + + public static void deleteDependencies(T entity) { + if (entity == null) return; + deletingEntities.clear(); + deleteDependenciesRecursive(entity); + } + /** * Recursively resolves dependencies for an entity, ensuring that * dependencies are created in the correct order (bottom-up). @@ -48,20 +70,21 @@ public static T resolveDependencies(T entity) { * @param The entity type * @return The entity with all dependencies resolved */ - private static T resolveDependenciesRecursive(T entity) { + private static T buildDependenciesRecursive(T entity) { if (entity == null) { return null; } - + Class entityClass = entity.getClass(); - + Log.info("Building dependencies for entity: " + entityClass.getSimpleName()); + // Check for circular dependency - if (resolvingEntities.contains(entityClass)) { + if (buildingEntities.contains(entityClass)) { throw new RuntimeException("Circular dependency detected for entity: " + entityClass.getSimpleName()); } // Add to resolving set - resolvingEntities.add(entityClass); + buildingEntities.add(entityClass); try { // Get all fields with @Dependency annotations @@ -69,13 +92,75 @@ private static T resolveDependenciesRecursive(T entity) { // Resolve each dependency field for (Field field : dependencyFields) { - resolveFieldDependencyRecursive(entity, field); + buildFieldDependencyRecursive(entity, field); } return entity; } finally { // Remove from resolving set - resolvingEntities.remove(entityClass); + buildingEntities.remove(entityClass); + } + } + + private static T createDependenciesRecursive(T entity) { + if (entity == null) { + return null; + } + + Class entityClass = entity.getClass(); + + // Check for circular dependency + if (creatingEntities.contains(entityClass)) { + throw new RuntimeException("Circular dependency detected for entity: " + entityClass.getSimpleName()); + } + + // Add to resolving set + creatingEntities.add(entityClass); + + try { + // Get all fields with @Dependency annotations + List dependencyFields = getDependencyFields(entityClass); + + // Resolve each dependency field + for (Field field : dependencyFields) { + createFieldDependencyRecursive(entity, field); + } + + return entity; + } finally { + // Remove from resolving set + creatingEntities.remove(entityClass); + } + } + + private static T deleteDependenciesRecursive(T entity) { + if (entity == null) { + return null; + } + + Class entityClass = entity.getClass(); + + // Check for circular dependency + if (deletingEntities.contains(entityClass)) { + throw new RuntimeException("Circular dependency detected for entity: " + entityClass.getSimpleName()); + } + + // Add to resolving set + deletingEntities.add(entityClass); + + try { + // Get all fields with @Dependency annotations + List dependencyFields = getDependencyFields(entityClass); + + // Resolve each dependency field + for (Field field : dependencyFields) { + deleteFieldDependencyRecursive(entity, field); + } + + return entity; + } finally { + // Remove from resolving set + deletingEntities.remove(entityClass); } } @@ -106,7 +191,7 @@ private static List getDependencyFields(Class entityClass) { * @param entity The parent entity * @param field The field that requires dependency resolution */ - private static void resolveFieldDependencyRecursive(Object entity, Field field) { + private static void buildFieldDependencyRecursive(Object entity, Field field) { try { field.setAccessible(true); Dependency dependencyAnnotation = field.getAnnotation(Dependency.class); @@ -116,31 +201,105 @@ private static void resolveFieldDependencyRecursive(Object entity, Field field) if (currentValue != null && !dependencyAnnotation.forceCreate()) { return; } - + + Log.info("Building dependency for field '" + field.getName() + "' of type '" + dependencyAnnotation.entityType().getSimpleName() + "'."); // Create the dependency entity using the factory - Object dependencyEntity = createDependencyEntity(dependencyAnnotation); + Object dependencyEntity = buildDependencyEntity(dependencyAnnotation); // Recursively resolve dependencies for the dependency entity // This ensures that all dependencies are resolved bottom-up - dependencyEntity = resolveDependenciesRecursive(dependencyEntity); - + dependencyEntity = buildDependenciesRecursive(dependencyEntity); +// +// // Get the factory directly using the entity type from the annotation +// @SuppressWarnings("unchecked") +// EntityFactory factory = (EntityFactory) FactoryProvider.INSTANCE.get((Class) dependencyAnnotation.entityType()); +// +// // Create the fully resolved entity in the factory (this will set the ID) +// Entity builtEntity = factory.buildDefault(); + + // Set the ID in the field + Log.info("Setting dependency field '" + field.getName() + "' with ID from built entity."); + + if(trySetViaSetter(entity, field, dependencyEntity)) { + Log.info("Set the dependency field '" + field.getName() + "' via setter."); + } else { + field.set(entity, dependencyEntity); + Log.info("Set the dependency field '" + field.getName() + "' directly."); + } + + } catch (Exception e) { + throw new RuntimeException("Failed to resolve dependency for field: " + field.getName(), e); + } + } + + private static void createFieldDependencyRecursive(Object entity, Field field) { + try { + field.setAccessible(true); + Dependency dependencyAnnotation = field.getAnnotation(Dependency.class); + Entity currentValue = (Entity)field.get(entity); + + // Skip if field already has a value and forceCreate is false + if (currentValue != null && !dependencyAnnotation.forceCreate() && currentValue.getIdentifier() != null) { + return; + } + + // Create the dependency entity using the repository + Object builtDependencyEntity = currentValue == null ? buildDependencyEntity(dependencyAnnotation) : currentValue; + + // Recursively resolve dependencies for the dependency entity + // This ensures that all dependencies are resolved bottom-up + builtDependencyEntity = createDependenciesRecursive(builtDependencyEntity); + // Get the repository directly using the entity type from the annotation @SuppressWarnings("unchecked") - Repository repository = (Repository) RepositoryFactory.INSTANCE.getRepository((Class) dependencyAnnotation.entityType()); - - // Create the fully resolved entity in the repository (this will set the ID) - Entity createdEntity = repository.create((Entity) dependencyEntity); - - // Extract the ID from the created entity - String entityId = extractEntityId(createdEntity); - - // Set the ID in the field - field.set(entity, entityId); - + Repository repository = (Repository) RepositoryProvider.INSTANCE.get((Class) dependencyAnnotation.entityType()); + + Log.info("Creating dependency entity for field '" + field.getName() + "' of type '" + dependencyAnnotation.entityType().getSimpleName() + "'."); + Entity createdEntity = repository.create((Entity) builtDependencyEntity); + ((Entity) builtDependencyEntity).setIdentifier((String)createdEntity.getIdentifier()); + + if (trySetViaSetter(entity, field, builtDependencyEntity)) { + Log.info("Set the dependency field '" + field.getName() + "' via setter."); + } else { + field.set(entity, builtDependencyEntity); + Log.info("Set the dependency field '" + field.getName() + "' directly."); + } + } catch (Exception e) { throw new RuntimeException("Failed to resolve dependency for field: " + field.getName(), e); } } + + private static void deleteFieldDependencyRecursive(Object entity, Field field) { + try { + field.setAccessible(true); + Dependency dependencyAnnotation = field.getAnnotation(Dependency.class); + Entity currentValue = (Entity)field.get(entity); + + // Skip if field already has a value and forceCreate is false + if (currentValue == null || currentValue.getIdentifier() == null) { + return; + } + + // Create the dependency entity using the repository + Object builtDependencyEntity = currentValue == null ? buildDependencyEntity(dependencyAnnotation) : currentValue; + + // Get the repository directly using the entity type from the annotation + @SuppressWarnings("unchecked") + Repository repository = (Repository) RepositoryProvider.INSTANCE.get((Class) dependencyAnnotation.entityType()); + + Log.info("Deleting dependency entity for field '" + field.getName() + "' of type '" + dependencyAnnotation.entityType().getSimpleName() + "'."); + repository.delete((Entity) builtDependencyEntity); + + // Recursively resolve dependencies for the dependency entity + // This ensures that all dependencies are resolved bottom-up + deleteDependenciesRecursive(builtDependencyEntity); + + + } catch (Exception e) { + throw new RuntimeException("Failed to delete dependency for field: " + field.getName(), e); + } + } /** * Creates a dependency entity using the specified factory method. @@ -148,7 +307,7 @@ private static void resolveFieldDependencyRecursive(Object entity, Field field) * @param dependencyAnnotation The dependency annotation containing creation info * @return The created dependency entity */ - private static Object createDependencyEntity(Dependency dependencyAnnotation) { + private static Object buildDependencyEntity(Dependency dependencyAnnotation) { try { // Find the factory for the entity type EntityFactory factory = findFactoryForEntity(dependencyAnnotation.entityType()); @@ -206,4 +365,45 @@ private static String extractEntityId(Object entity) { throw new RuntimeException("Failed to extract ID from entity: " + entity.getClass().getSimpleName(), e); } } + + private static boolean trySetViaSetter(Object target, Field field, Object value) { + Class clazz = target.getClass(); + Class fieldType = field.getType(); + List candidates = new ArrayList<>(); + + String fieldName = field.getName(); + String cap = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + + // Conventional JavaBean setter + candidates.add("set" + cap); + + // Boolean field named like "isActive" → setter "setActive" + if ((fieldType == boolean.class || fieldType == Boolean.class) + && fieldName.startsWith("is") + && fieldName.length() > 2 + && Character.isUpperCase(fieldName.charAt(2))) { + candidates.add("set" + fieldName.substring(2)); + } + + // Fluent/Lombok-style setter (e.g., ".name(value)") + candidates.add(fieldName); + + // Try to find a compatible method (single parameter assignable from value) + for (String name : candidates) { + for (Method m : clazz.getMethods()) { // includes inherited public methods + if (!m.getName().equals(name) || m.getParameterCount() != 1) continue; + Class paramType = m.getParameterTypes()[0]; + if (value == null ? !paramType.isPrimitive() : paramType.isAssignableFrom(value.getClass())) { + try { + if (!m.canAccess(target)) m.setAccessible(true); + m.invoke(target, value); + return true; + } catch (ReflectiveOperationException ignore) { + // try next candidate + } + } + } + } + return false; + } } \ No newline at end of file diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/Entity.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/Entity.java index 2e991b6c..7e399384 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/Entity.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/Entity.java @@ -1,31 +1,45 @@ package solutions.bellatrix.data.http.infrastructure; import lombok.experimental.SuperBuilder; -import solutions.bellatrix.data.configuration.RepositoryFactory; +import solutions.bellatrix.data.configuration.RepositoryProvider; import solutions.bellatrix.data.contracts.Repository; @SuperBuilder @SuppressWarnings("unchecked") public abstract class Entity { public TEntity get() { - var repository = (Repository)RepositoryFactory.INSTANCE.getRepository(this.getClass()); + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); return (TEntity)repository.getById(this); } public TEntity create() { - var repository = (Repository)RepositoryFactory.INSTANCE.getRepository(this.getClass()); + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); return (TEntity)repository.create(this); } + public TEntity createWithDependencies() { + DependencyResolver.createDependencies(this); + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); + this.setIdentifier((String)repository.create(this).getIdentifier()); + return (TEntity)this; + } + public TEntity update() { - var repository = (Repository)RepositoryFactory.INSTANCE.getRepository(this.getClass()); + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); return (TEntity)repository.update(this); } public void delete() { - var repository = (Repository)RepositoryFactory.INSTANCE.getRepository(this.getClass()); + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); + repository.delete(this); + } + + public void deleteDependenciesAndSelf() { + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); repository.delete(this); + DependencyResolver.deleteDependencies(this); } public abstract TIdentifier getIdentifier(); + public abstract void setIdentifier(String id); } \ No newline at end of file diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpRepository.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpRepository.java index a8048aaa..455e1351 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpRepository.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpRepository.java @@ -58,8 +58,9 @@ var record = (THttpEntity)deserializeInternal(response, DeserializationMode.SING @Override public THttpEntity create(THttpEntity entity) { + THttpEntity finalEntity = entity; updateRequestContext(requestContext -> { - requestContext.addRequestBody((objectConverter.toString(entity))); + requestContext.addRequestBody((objectConverter.toString(finalEntity))); requestContext.addRequestMethod(POST); }); @@ -69,7 +70,7 @@ public THttpEntity create(THttpEntity entity) { var record = (THttpEntity)deserializeInternal(response, DeserializationMode.SINGLE); ENTITY_CREATED.broadcast(new EntityCreatedEventArgs(record)); - + entity = record; return record; } diff --git a/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java index c885c178..ff9528a1 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/plugins/TestDataCleanupPlugin.java @@ -18,7 +18,7 @@ import solutions.bellatrix.core.plugins.TestResult; import solutions.bellatrix.core.plugins.TimeRecord; import solutions.bellatrix.core.utilities.Log; -import solutions.bellatrix.data.configuration.RepositoryFactory; +import solutions.bellatrix.data.configuration.RepositoryProvider; import solutions.bellatrix.data.contracts.Repository; import solutions.bellatrix.data.http.infrastructure.Entity; import solutions.bellatrix.data.http.infrastructure.HttpEntity; @@ -264,8 +264,8 @@ private void cleanupEntity(EntityInfo entityInfo) { */ @SuppressWarnings("unchecked") private Repository getRepository(Class> entityClass) { - return REPOSITORY_CACHE.computeIfAbsent(entityClass, - clazz -> RepositoryFactory.INSTANCE.getRepository(clazz)); + return REPOSITORY_CACHE.computeIfAbsent(entityClass, + RepositoryProvider.INSTANCE::get); } /** diff --git a/bellatrix.playwright/pom.xml b/bellatrix.playwright/pom.xml index a8099293..86a72eab 100644 --- a/bellatrix.playwright/pom.xml +++ b/bellatrix.playwright/pom.xml @@ -23,11 +23,6 @@ playwright 1.49.0 - - org.junit.jupiter - junit-jupiter - 5.10.0 - solutions.bellatrix bellatrix.core @@ -52,16 +47,6 @@ 1.0 compile - - org.projectlombok - lombok - 1.18.30 - - - com.microsoft.playwright - playwright - 1.44.0 - org.junit.jupiter junit-jupiter diff --git a/bellatrix.plugins.opencv/pom.xml b/bellatrix.plugins.opencv/pom.xml index 66dafc6c..b00bf7f8 100644 --- a/bellatrix.plugins.opencv/pom.xml +++ b/bellatrix.plugins.opencv/pom.xml @@ -36,12 +36,6 @@ 4.3.0-3 compile - - jakarta.xml.bind - jakarta.xml.bind-api - 2.3.2 - test - jakarta.xml.bind jakarta.xml.bind-api diff --git a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/infrastructure/core/entities/ServiceNowEntity.java b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/infrastructure/core/entities/ServiceNowEntity.java index 372007ec..5e0cf9c6 100644 --- a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/infrastructure/core/entities/ServiceNowEntity.java +++ b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/infrastructure/core/entities/ServiceNowEntity.java @@ -41,4 +41,9 @@ public abstract class ServiceNowEntity extends public String getIdentifier() { return sysId; } + + @Override + public void setIdentifier(String id) { + this.sysId = id; + } } \ No newline at end of file diff --git a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/converters/FxCurrencyConverter.java b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/converters/FxCurrencyConverter.java index 8262a690..5962aaac 100644 --- a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/converters/FxCurrencyConverter.java +++ b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/converters/FxCurrencyConverter.java @@ -1,15 +1,10 @@ package solutions.bellatrix.servicenow.utilities.converters; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import solutions.bellatrix.data.configuration.RepositoryFactory; +import com.google.gson.*; +import solutions.bellatrix.data.configuration.RepositoryProvider; import solutions.bellatrix.servicenow.utilities.setupData.FxCurrency2Instance; import solutions.bellatrix.servicenow.utilities.setupData.FxCurrency2InstanceRepository; + import java.lang.reflect.Type; public class FxCurrencyConverter implements JsonSerializer, JsonDeserializer { @@ -31,7 +26,7 @@ public FxCurrency2Instance deserialize(JsonElement jsonElement, Type type, JsonD if (elementAsString == null || elementAsString.isEmpty()) { return null; } - RepositoryFactory.INSTANCE.registerRepository(FxCurrency2Instance.class, FxCurrency2InstanceRepository.class); + RepositoryProvider.INSTANCE.register(FxCurrency2Instance.class, FxCurrency2InstanceRepository.class); var repository = new FxCurrency2InstanceRepository(); return FxCurrency2Instance.builder().sysId(elementAsString).build().get(); } diff --git a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/setupData/FxCurrency2Instance.java b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/setupData/FxCurrency2Instance.java index ebe7a980..ba9a848b 100644 --- a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/setupData/FxCurrency2Instance.java +++ b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/utilities/setupData/FxCurrency2Instance.java @@ -41,4 +41,14 @@ public String toStringForTableApi() { public String toString() { return this.amount.toString(); } + + @Override + public String getIdentifier() { + return this.getSysId(); + } + + @Override + public void setIdentifier(String id) { + this.setSysId(id); + } } \ No newline at end of file diff --git a/framework-tests/bellatrix.data.tests/src/main/java/infrastructure/artist/Artist.java b/framework-tests/bellatrix.data.tests/src/main/java/infrastructure/artist/Artist.java index d132776c..72a93a79 100644 --- a/framework-tests/bellatrix.data.tests/src/main/java/infrastructure/artist/Artist.java +++ b/framework-tests/bellatrix.data.tests/src/main/java/infrastructure/artist/Artist.java @@ -20,4 +20,9 @@ public class Artist extends HttpEntity { public String getIdentifier() { return id; } + + @Override + public void setIdentifier(String id) { + this.id = id; + } } \ No newline at end of file diff --git a/getting-started/bellatrix.data.getting.started/src/main/java/Artist.java b/getting-started/bellatrix.data.getting.started/src/main/java/Artist.java index bdd9d90e..8468941d 100644 --- a/getting-started/bellatrix.data.getting.started/src/main/java/Artist.java +++ b/getting-started/bellatrix.data.getting.started/src/main/java/Artist.java @@ -17,4 +17,9 @@ public class Artist extends HttpEntity { public String getIdentifier() { return id; } + + @Override + public void setIdentifier(String id) { + this.id = id; + } } \ No newline at end of file diff --git a/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java b/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java index 188d88d1..c40533ad 100644 --- a/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java +++ b/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java @@ -1,11 +1,11 @@ import org.junit.jupiter.api.Test; -import solutions.bellatrix.data.configuration.RepositoryFactory; +import solutions.bellatrix.data.configuration.RepositoryProvider; public class BaseTest { @Test public void getAllResources_when_sendGetAlRequest() { - RepositoryFactory.INSTANCE.registerRepository(Artist.class, ArtistRepository.class); + RepositoryProvider.INSTANCE.register(Artist.class, ArtistRepository.class); var artistRepository = new ArtistRepository(); Artist artist = Artist.builder().name("James Clavell").build().create(); From 7068c0bae008c205df26bb80f9cfa604847d1efe Mon Sep 17 00:00:00 2001 From: NikolayAvramov Date: Fri, 26 Sep 2025 14:28:49 +0300 Subject: [PATCH 3/3] fix comments --- .../bellatrix.data.getting.started/src/test/java/BaseTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java b/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java index c40533ad..40fdba6e 100644 --- a/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java +++ b/getting-started/bellatrix.data.getting.started/src/test/java/BaseTest.java @@ -6,9 +6,8 @@ public class BaseTest { @Test public void getAllResources_when_sendGetAlRequest() { RepositoryProvider.INSTANCE.register(Artist.class, ArtistRepository.class); - var artistRepository = new ArtistRepository(); Artist artist = Artist.builder().name("James Clavell").build().create(); - artist.getResponse().getNativeResponse(); + assert artist.getResponse().getNativeResponse().statusCode() == 200; } } \ No newline at end of file