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 013389ca..d363150c 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 @@ -25,7 +25,8 @@ public class DependencyResolver { private static final Set> buildingEntities = new HashSet<>(); private static final Set> creatingEntities = new HashSet<>(); private static final Set> deletingEntities = new HashSet<>(); - + private static final Set> updatingEntities = new HashSet<>(); + /** * Resolves all dependencies for the given entity by creating * dependent entities where fields are marked with @Dependency @@ -56,6 +57,13 @@ public static T createDependencies(T entity) { return createDependenciesRecursive(entity); } + public static T updateDependencies(T entity) { + if (entity == null) { + return null; + } + return updateDependenciesRecursive(entity); + } + public static void deleteDependencies(T entity) { if (entity == null) return; deletingEntities.clear(); @@ -163,6 +171,38 @@ private static T deleteDependenciesRecursive(T entity) { deletingEntities.remove(entityClass); } } + + private static T updateDependenciesRecursive(T entity) { + if (entity == null) { + return null; + } + + Class entityClass = entity.getClass(); + + // Check for circular dependency + if (updatingEntities.contains(entityClass)) { + throw new RuntimeException("Circular dependency detected for entity: " + entityClass.getSimpleName()); + } + + // Add to resolving set + updatingEntities.add(entityClass); + + try { + // Get all fields with @Dependency annotations + List dependencyFields = getDependencyFields(entityClass); + + // Resolve each dependency field + for (Field field : dependencyFields) { + updateFieldDependencyRecursive(entity, field); + } + + return entity; + } finally { + // Remove from resolving set + updatingEntities.remove(entityClass); + } + } + /** * Gets all fields with @Dependency annotations from the given class. @@ -170,7 +210,7 @@ private static T deleteDependenciesRecursive(T entity) { * @param entityClass The entity class to scan * @return List of fields with @Dependency annotations */ - private static List getDependencyFields(Class entityClass) { + protected static List getDependencyFields(Class entityClass) { List dependencyFields = new ArrayList<>(); Field[] fields = entityClass.getDeclaredFields(); @@ -333,7 +373,7 @@ private static Object buildDependencyEntity(Dependency dependencyAnnotation) { * @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) { + public static EntityFactory findFactoryForEntity(Class entityType) { String entityName = entityType.getSimpleName(); String factoryClassName = entityName + "RepositoryFactory"; @@ -406,4 +446,34 @@ private static boolean trySetViaSetter(Object target, Field field, Object value) } return false; } + + private static void updateFieldDependencyRecursive(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("Updating dependency entity for field '" + field.getName() + "' of type '" + dependencyAnnotation.entityType().getSimpleName() + "'."); + repository.update((Entity) builtDependencyEntity); + + // Recursively resolve dependencies for the dependency entity + // This ensures that all dependencies are resolved bottom-up + updateDependenciesRecursive(builtDependencyEntity); + + } catch (Exception e) { + throw new RuntimeException("Failed to update dependency for field: " + field.getName(), e); + } + } } \ 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 7e399384..16f9eb4a 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,10 +1,20 @@ package solutions.bellatrix.data.http.infrastructure; +import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; +import solutions.bellatrix.data.annotations.Dependency; 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.InvocationTargetException; +import java.util.List; + +import static solutions.bellatrix.data.http.infrastructure.DependencyResolver.getDependencyFields; @SuperBuilder +@NoArgsConstructor @SuppressWarnings("unchecked") public abstract class Entity { public TEntity get() { @@ -40,6 +50,48 @@ public void deleteDependenciesAndSelf() { DependencyResolver.deleteDependencies(this); } + public TEntity getWithDependencies() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException { + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); + var record = repository.getById(this); + List dependencyFields = getDependencyFields(record.getClass()); + + for (Field field : dependencyFields) { + field.setAccessible(true); + Dependency dependencyAnnotation = field.getAnnotation(Dependency.class); + + Entity currentValue = (Entity)field.get(this); + // Skip if field already has a value and forceCreate is false + if (currentValue != null && !dependencyAnnotation.forceCreate()) { + break; + } + + Field f = record.getClass().getDeclaredField(String.format("id%s", Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1))); + f.setAccessible(true); + var dependencyId = f.get(record).toString(); + + Class fieldType = field.getType(); + EntityFactory factory = DependencyResolver.findFactoryForEntity(fieldType); + if (factory == null) { + throw new IllegalStateException("No factory found for entity type: " + dependencyAnnotation.entityType().getSimpleName()); + } + + Entity newEntity = factory.buildDefault(); + + newEntity.setIdentifier(dependencyId); + + field.set(record, newEntity.getWithDependencies()); + } + + return (TEntity) record; + } + + public TEntity updateWithDependencies() { + DependencyResolver.updateDependencies(this); + var repository = (Repository)RepositoryProvider.INSTANCE.get(this.getClass()); + this.setIdentifier((String)repository.update(this).getIdentifier()); + return (TEntity)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/HttpEntity.java b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpEntity.java index f3b06964..de3ca725 100644 --- a/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpEntity.java +++ b/bellatrix.data/src/main/java/solutions/bellatrix/data/http/infrastructure/HttpEntity.java @@ -2,6 +2,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import solutions.bellatrix.data.http.contracts.Queryable; @@ -9,6 +10,7 @@ @Data @SuperBuilder +@NoArgsConstructor @EqualsAndHashCode(callSuper = true) public abstract class HttpEntity extends Entity implements Queryable { private transient HttpResponse response; diff --git a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/enums/UibToolbarButtonLabel.java b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/enums/UibToolbarButtonLabel.java new file mode 100644 index 00000000..711b61ed --- /dev/null +++ b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/enums/UibToolbarButtonLabel.java @@ -0,0 +1,23 @@ +package solutions.bellatrix.servicenow.components.enums; + +import lombok.Getter; + +@Getter +public enum UibToolbarButtonLabel { + HOME("Home"), + LIST("List"), + TEAMS("Teams"), + SCHEDULES("Schedules"), + DASHBOARD("Dashboard"); + + private final String value; + + UibToolbarButtonLabel(String label) { + this.value = label; + } + + @Override + public String toString() { + return value; + } +} diff --git a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordCheckbox.java b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordCheckbox.java index 80c6b345..6c042c39 100644 --- a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordCheckbox.java +++ b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordCheckbox.java @@ -1,9 +1,9 @@ package solutions.bellatrix.servicenow.components.uiBuilder; +import solutions.bellatrix.servicenow.components.enums.UibComponentType; import solutions.bellatrix.web.components.*; import solutions.bellatrix.web.components.contracts.ComponentDisabled; -public class RecordCheckbox extends WebComponent implements ComponentDisabled { - +public class RecordCheckbox extends UIBDefaultComponent implements ComponentDisabled { protected CheckBox checkbox() { return this.createByCss(CheckBox.class, "input[type='checkbox']"); } @@ -12,11 +12,20 @@ public boolean isChecked() { return checkbox().isChecked(); } + @Override + public void setText(String text) { + } + @Override public boolean isDisabled() { return getAttribute("readonly") != null || checkbox().isDisabled() || getAttribute("disabled") != null; } + @Override + public UibComponentType componentType() { + return null; + } + public void check() { if (!isChecked() && !isDisabled()) { checkbox().check(); @@ -40,4 +49,9 @@ public void assertIsChecked() { public void assertIsUnchecked() { checkbox().validateIsUnchecked(); } + + @Override + public String getText() { + return ""; + } } \ No newline at end of file diff --git a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordChoice.java b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordChoice.java index 379b207c..3dfb54a2 100644 --- a/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordChoice.java +++ b/bellatrix.servicenow/src/main/java/solutions/bellatrix/servicenow/components/uiBuilder/RecordChoice.java @@ -10,23 +10,23 @@ public class RecordChoice extends UIBDefaultComponent implements ComponentDisabled, ComponentText { - protected WebComponent dropdown() { + public WebComponent dropdown() { return this.createByXPath(WebComponent.class, ".//now-select"); } - protected Button dropdownButton() { + public Button dropdownButton() { return dropdown().createByCss(Button.class, "button"); } - protected List