1+ package solutions .bellatrix .data .http .infrastructure ;
2+
3+ import solutions .bellatrix .data .annotations .Dependency ;
4+ import solutions .bellatrix .data .configuration .RepositoryFactory ;
5+ import solutions .bellatrix .data .contracts .Repository ;
6+ import solutions .bellatrix .data .http .contracts .EntityFactory ;
7+
8+ import java .lang .reflect .Field ;
9+ import java .lang .reflect .Method ;
10+ import java .util .*;
11+
12+ /**
13+ * Utility class for automatically resolving entity dependencies.
14+ * This class handles the creation of dependent entities when fields
15+ * are marked with @Dependency annotations using recursive resolution
16+ * to handle complex dependency chains.
17+ */
18+ public class DependencyResolver {
19+
20+ // Track entities being resolved to prevent circular dependencies
21+ private static final Set <Class <?>> resolvingEntities = new HashSet <>();
22+
23+ /**
24+ * Resolves all dependencies for the given entity by creating
25+ * dependent entities where fields are marked with @Dependency
26+ * and the field value is null. Uses recursive resolution to handle
27+ * complex dependency chains.
28+ *
29+ * @param entity The entity to resolve dependencies for
30+ * @param <T> The entity type
31+ * @return The entity with all dependencies resolved
32+ */
33+ public static <T > T resolveDependencies (T entity ) {
34+ if (entity == null ) {
35+ return null ;
36+ }
37+
38+ // Clear the resolving set for each top-level call
39+ resolvingEntities .clear ();
40+ return resolveDependenciesRecursive (entity );
41+ }
42+
43+ /**
44+ * Recursively resolves dependencies for an entity, ensuring that
45+ * dependencies are created in the correct order (bottom-up).
46+ *
47+ * @param entity The entity to resolve dependencies for
48+ * @param <T> The entity type
49+ * @return The entity with all dependencies resolved
50+ */
51+ private static <T > T resolveDependenciesRecursive (T entity ) {
52+ if (entity == null ) {
53+ return null ;
54+ }
55+
56+ Class <?> entityClass = entity .getClass ();
57+
58+ // Check for circular dependency
59+ if (resolvingEntities .contains (entityClass )) {
60+ throw new RuntimeException ("Circular dependency detected for entity: " + entityClass .getSimpleName ());
61+ }
62+
63+ // Add to resolving set
64+ resolvingEntities .add (entityClass );
65+
66+ try {
67+ // Get all fields with @Dependency annotations
68+ List <Field > dependencyFields = getDependencyFields (entityClass );
69+
70+ // Resolve each dependency field
71+ for (Field field : dependencyFields ) {
72+ resolveFieldDependencyRecursive (entity , field );
73+ }
74+
75+ return entity ;
76+ } finally {
77+ // Remove from resolving set
78+ resolvingEntities .remove (entityClass );
79+ }
80+ }
81+
82+ /**
83+ * Gets all fields with @Dependency annotations from the given class.
84+ *
85+ * @param entityClass The entity class to scan
86+ * @return List of fields with @Dependency annotations
87+ */
88+ private static List <Field > getDependencyFields (Class <?> entityClass ) {
89+ List <Field > dependencyFields = new ArrayList <>();
90+ Field [] fields = entityClass .getDeclaredFields ();
91+
92+ for (Field field : fields ) {
93+ if (field .getAnnotation (Dependency .class ) != null ) {
94+ dependencyFields .add (field );
95+ }
96+ }
97+
98+ return dependencyFields ;
99+ }
100+
101+ /**
102+ * Recursively resolves a single field dependency by creating the required entity
103+ * and setting its ID in the field. This method ensures that the dependency entity
104+ * is fully resolved (including its own dependencies) before being created.
105+ *
106+ * @param entity The parent entity
107+ * @param field The field that requires dependency resolution
108+ */
109+ private static void resolveFieldDependencyRecursive (Object entity , Field field ) {
110+ try {
111+ field .setAccessible (true );
112+ Dependency dependencyAnnotation = field .getAnnotation (Dependency .class );
113+ Object currentValue = field .get (entity );
114+
115+ // Skip if field already has a value and forceCreate is false
116+ if (currentValue != null && !dependencyAnnotation .forceCreate ()) {
117+ return ;
118+ }
119+
120+ // Create the dependency entity using the factory
121+ Object dependencyEntity = createDependencyEntity (dependencyAnnotation );
122+
123+ // Recursively resolve dependencies for the dependency entity
124+ // This ensures that all dependencies are resolved bottom-up
125+ dependencyEntity = resolveDependenciesRecursive (dependencyEntity );
126+
127+ // Get the repository directly using the entity type from the annotation
128+ @ SuppressWarnings ("unchecked" )
129+ Repository <Entity > repository = (Repository <Entity >) RepositoryFactory .INSTANCE .getRepository ((Class <? extends Entity >) dependencyAnnotation .entityType ());
130+
131+ // Create the fully resolved entity in the repository (this will set the ID)
132+ Entity createdEntity = repository .create ((Entity ) dependencyEntity );
133+
134+ // Extract the ID from the created entity
135+ String entityId = extractEntityId (createdEntity );
136+
137+ // Set the ID in the field
138+ field .set (entity , entityId );
139+
140+ } catch (Exception e ) {
141+ throw new RuntimeException ("Failed to resolve dependency for field: " + field .getName (), e );
142+ }
143+ }
144+
145+ /**
146+ * Creates a dependency entity using the specified factory method.
147+ *
148+ * @param dependencyAnnotation The dependency annotation containing creation info
149+ * @return The created dependency entity
150+ */
151+ private static Object createDependencyEntity (Dependency dependencyAnnotation ) {
152+ try {
153+ // Find the factory for the entity type
154+ EntityFactory <?> factory = findFactoryForEntity (dependencyAnnotation .entityType ());
155+ if (factory == null ) {
156+ throw new IllegalStateException ("No factory found for entity type: " + dependencyAnnotation .entityType ().getSimpleName ());
157+ }
158+
159+ // Use the specified factory method or default
160+ String methodName = dependencyAnnotation .factoryMethod ();
161+ Method factoryMethod = factory .getClass ().getMethod (methodName );
162+
163+ return factoryMethod .invoke (factory );
164+
165+ } catch (Exception e ) {
166+ throw new RuntimeException ("Failed to create dependency entity" , e );
167+ }
168+ }
169+
170+ /**
171+ * Finds the factory for the given entity type by looking for
172+ * factory classes that implement EntityFactory.
173+ *
174+ * @param entityType The entity class to find a factory for
175+ * @return The factory instance or null if not found
176+ */
177+ private static EntityFactory <?> findFactoryForEntity (Class <?> entityType ) {
178+ String entityName = entityType .getSimpleName ();
179+ String factoryClassName = entityName + "RepositoryFactory" ;
180+
181+ try {
182+ // Try to find the factory in the same package as the entity
183+ String packageName = entityType .getPackage ().getName ();
184+ Class <?> factoryClass = Class .forName (packageName + "." + factoryClassName );
185+
186+ // For static classes, create a temporary instance to call methods
187+ return (EntityFactory <?>) factoryClass .getDeclaredConstructor ().newInstance ();
188+
189+ } catch (Exception e ) {
190+ throw new RuntimeException ("Factory not found for entity type: " + entityType .getSimpleName (), e );
191+ }
192+ }
193+
194+ /**
195+ * Extracts the ID from an entity object by calling the getId() method.
196+ *
197+ * @param entity The entity to extract ID from
198+ * @return The entity ID as a string
199+ */
200+ private static String extractEntityId (Object entity ) {
201+ try {
202+ Method getIdMethod = entity .getClass ().getMethod ("getId" );
203+ Object id = getIdMethod .invoke (entity );
204+ return id != null ? id .toString () : null ;
205+ } catch (Exception e ) {
206+ throw new RuntimeException ("Failed to extract ID from entity: " + entity .getClass ().getSimpleName (), e );
207+ }
208+ }
209+ }
0 commit comments