mardi 4 septembre 2018

Accessing the correct Spring service when only knowing the entity type

I built an extension of the Tab class (JavaFX) that, with a few parameters such as the entity class type, literally build an entire GUI around the entity class (table to display, functional add / edit / delete buttons / functional search fields dynamically created from class members / etc). This class needs to be extended in order to be adapted to each entity type but so far i have 4 entities using it and i'm very happy with the results, it saves a LOT of code.

One issue I have is around the dynamic creation of the search fields when initializing the tab. What i do is iterate through the entity class' fields and create either a TextField to search on a String field or a ComboBox filled with the different options if the field has a ManyToOne relationship with another entity type. In order to achieve this, i built a marker interface called TFHService which all of my entities implement. I also have a @HiddenField annotation to exclude fields like IDs, Set for bi-directional mapping, etc. See the code below (note, frienly name is just to give the prompt text a french language since my field names are in english) :

//Create Search Fields Based on Entity Class Members and Annotations
    int i = 0;
    for (Field field : entityClass.getDeclaredFields())
    {
        if (!isFieldHidden(field))
        {
            if (isFieldEntityType(field))
            {
                FriendlyName friendlyName = field.getAnnotation(FriendlyName.class);
                ComboBox searchField = new ComboBox();
                searchField.setPromptText("Recherche " + friendlyName.value());
                searchField.setMaxWidth(Double.MAX_VALUE);
                searchField.getSelectionModel().selectedItemProperty().addListener((observable) -> 
                {
                    showEntities();
                });
                if (field.getType().getName().contains("Category"))
                {
                    ObservableList<Category> categories = FXCollections.observableArrayList(categoryService.findAll());
                    categories.add(0, new Category(null, "Recherche " + friendlyName.value(), null, null));
                    searchField.setItems(categories);
                }
                else if (field.getType().getName().contains("Supplier"))
                {
                    ObservableList<Supplier> fournisseurs = FXCollections.observableArrayList(supplierService.findAll());
                    fournisseurs.add(0, new Supplier(null, "Recherche " + friendlyName.value(), null, null, null, null, null, null));
                    searchField.setItems(fournisseurs);
                }

                searchFields.put(i, searchField);
            }
            else
            {
                FriendlyName friendlyName = field.getAnnotation(FriendlyName.class);
                TextField searchField = new TextField();
                searchField.setPromptText("Recherche " + friendlyName.value());
                searchField.setOnKeyReleased((event) -> 
                {
                    showEntities();
                });

                searchFields.put(i, searchField);
            }

            i++;
        }
    }


private boolean isFieldEntityType(Field field)
{
    for (Class c : field.getType().getInterfaces())
    {
        if (c.getName().contains("TFHEntity"))
        {
            return true;
        }
    }

    return false;
}

private boolean isFieldHidden(Field field)
{
    HiddenField hiddenField = field.getAnnotation(HiddenField.class);

    return hiddenField != null;
}

The way I have to construct my ObservableLists for the ComboBoxes is what bothers me. I would like to have a way to DYNAMICALLY call the right service's findAll() method to populate these lists, without needing to check the type and repeating code like i'm doing now. I have build a map with the Entity class type as the key and the service as the value (they obviously all need to be autowired too) but i'm struggling to think of a way to achieve this :

private final Map<Class, TFHService> serviceMap = new HashMap<>();

private void initServiceMap()
{
    serviceMap.put(Bill.class, billService);
    serviceMap.put(Category.class, categoryService);
    serviceMap.put(Customer.class, customerService);
    serviceMap.put(Parameter.class, parameterService);
    serviceMap.put(Product.class, productService);
    serviceMap.put(Sequence.class, sequenceService);
    serviceMap.put(Supplier.class, supplierService);
}

I am sure reflection would allow me to do this (i know there is an invoke method somewhere) but I'm not savvy enough on the topic to put everything i know together and achieve this. I learned a LOT today, my brain hurts quite a bit, I'm very happy with what I achieved but this limits expandability since i need to modify the super class for every type of ComboBox.

I hope this is clear, this code seems very complex to me and i struggled to put my question into words. Please help if you know of a way to somehow invoke the right service's findAll() method using what i've built so far and without the ugly, limiting if / else statement.

Thanks!





Aucun commentaire:

Enregistrer un commentaire