All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.devbench.uibuilder.spring.crud.SpringGenericItemCrudControllerBean Maven / Gradle / Ivy

The newest version!
/*
 *
 * Copyright © 2018 Webvalto Ltd.
 *
 * 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 io.devbench.uibuilder.spring.crud;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.spring.annotation.UIScope;
import io.devbench.uibuilder.annotations.ControllerBean;
import io.devbench.uibuilder.api.components.HasRawElementComponent;
import io.devbench.uibuilder.api.components.form.UIBuilderDetailCapable;
import io.devbench.uibuilder.api.components.masterconnector.UIBuilderMasterConnector;
import io.devbench.uibuilder.api.controllerbean.uieventhandler.Item;
import io.devbench.uibuilder.api.controllerbean.uieventhandler.Source;
import io.devbench.uibuilder.api.controllerbean.uieventhandler.UIEventHandler;
import io.devbench.uibuilder.api.crud.CanCreate;
import io.devbench.uibuilder.api.crud.GenericItemCrudControllerBean;
import io.devbench.uibuilder.api.crud.MasterConnectorProvider;
import io.devbench.uibuilder.api.crud.Refreshable;
import io.devbench.uibuilder.api.member.scanner.MemberScanner;
import io.devbench.uibuilder.core.parse.elementwalker.ElementWalker;
import io.devbench.uibuilder.core.utils.reflection.ClassMetadata;
import io.devbench.uibuilder.core.utils.reflection.PropertyMetadata;
import io.devbench.uibuilder.spring.crud.exception.GenericItemCrudControllerException;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import javax.persistence.OneToMany;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

@Slf4j
@UIScope
@ControllerBean(GenericItemCrudControllerBean.BUILT_IN_GENERIC_ITEM_CRUD_PANEL_CONTROLLER_BEAN_NAME)
public class SpringGenericItemCrudControllerBean implements GenericItemCrudControllerBean {

    public static final String ITEM_BIND = "item-bind";
    public static final String ITEM_BIND_ITEMS_PREFIX = "items:";
    public static final String ITEM_DATA_SOURCE = "item-data-source";
    private final Map> joinItemSuppliers;

    public SpringGenericItemCrudControllerBean() {
        this.joinItemSuppliers = new HashMap<>();
    }

    @Override
    public void registerJoinItemSupplier(@NotNull String mdcId, @Nullable Supplier joinItemSupplier) {
        joinItemSuppliers.put(mdcId, joinItemSupplier);
    }

    @Override
    @UIEventHandler("onRefresh")
    public void refresh(@Source Refreshable refreshable) {
        if (refreshable != null) {
            refreshable.refresh();
        }
    }

    @Override
    @UIEventHandler("onSave")
    public void onSave(@Item TYPE subject, @Source Refreshable refreshable) {
        if (asItemCreateProvider(refreshable).isSelectedItemCreated()) {
            asMasterConnectorProvider(refreshable).getMasterConnector().addItem(subject);
        }
        refreshable.refresh();
    }

    @Override
    @UIEventHandler("onDelete")
    public void onDelete(@Item TYPE subject, @Source Refreshable refreshable) {
        // if the master connector is direct modifiable, then the remove on member list will be done automatically by the MDC
    }

    @Override
    @UIEventHandler("onCreate")
    public void onCreate(@Source Refreshable refreshable) {
        CanCreate itemCreateProvider = asItemCreateProvider(refreshable);
        UIBuilderMasterConnector masterConnector = asMasterConnectorProvider(refreshable).getMasterConnector();
        Component masterComponent = masterConnector.getMasterComponent();
        if (masterComponent instanceof HasRawElementComponent) {
            Element masterElement = ((HasRawElementComponent) masterComponent).getRawElement();
            findReferencedParentCollectionPropertyName(masterElement)
                .ifPresent(parentItemCollectionPropertyName -> findParentMdcId(masterElement)
                    .ifPresent(parentMdcId -> handleOneToManyRelation(itemCreateProvider, parentItemCollectionPropertyName, parentMdcId)));
        }
    }

    private Optional findParentMdcId(Element currentMasterElement) {
        AtomicReference foundParentMdcId = new AtomicReference<>();
        currentMasterElement.parents().stream()
            .filter(this::isElementDetailCapable)
            .findFirst()
            .map(Element::id)
            .ifPresent(detailPanelId -> {
                Element rootElement = (Element) currentMasterElement.root();
                ElementWalker.of(rootElement)
                    .withProcessor(element -> {
                        if ("master-detail-controller".equals(element.normalName()) && element.attr("detail").trim().equals(detailPanelId)) {
                            foundParentMdcId.set(element.id());
                        }
                        return new Elements(element);
                    }).walk();
            });
        return Optional.ofNullable(foundParentMdcId.get());
    }

    private boolean isElementDetailCapable(Element element) {
        return MemberScanner.getInstance().findClassesBySuperType(UIBuilderDetailCapable.class).stream()
            .filter(it -> it.isAnnotationPresent(Tag.class))
            .map(it -> it.getAnnotation(Tag.class).value())
            .anyMatch(it -> it.equalsIgnoreCase(element.normalName()));
    }

    @SuppressWarnings("unchecked")
    private MasterConnectorProvider asMasterConnectorProvider(Refreshable refreshable) {
        if (refreshable instanceof MasterConnectorProvider) {
            return (MasterConnectorProvider) refreshable;
        }
        throw new GenericItemCrudControllerException("Source is not a master connector provider");
    }

    @SuppressWarnings("unchecked")
    private CanCreate asItemCreateProvider(Refreshable refreshable) {
        if (refreshable instanceof CanCreate) {
            return (CanCreate) refreshable;
        }
        throw new GenericItemCrudControllerException("Source is not an item create provider");
    }

    private Optional findReferencedParentCollectionPropertyName(Element masterElement) {
        String masterComponentItemBind = masterElement.attr(ITEM_BIND).trim();
        if (masterComponentItemBind.startsWith(ITEM_BIND_ITEMS_PREFIX)) {
            return Optional.of(masterComponentItemBind.substring(ITEM_BIND_ITEMS_PREFIX.length()));
        } else {
            return masterElement.children().stream()
                .filter(element -> ITEM_DATA_SOURCE.equals(element.normalName()))
                .filter(element -> element.hasAttr(ITEM_BIND))
                .filter(element -> element.attr(ITEM_BIND).trim().startsWith(ITEM_BIND_ITEMS_PREFIX))
                .map(element -> element.attr(ITEM_BIND).trim().substring(ITEM_BIND_ITEMS_PREFIX.length()))
                .findFirst();
        }
    }

    private void handleOneToManyRelation(CanCreate itemCreateProvider, String parentItemCollectionPropertyName, String parentMdcId) {
        Supplier parentSelectedItemSupplier = joinItemSuppliers.get(parentMdcId);
        if (parentSelectedItemSupplier != null) {
            Object parentSelectedItem = parentSelectedItemSupplier.get();

            ClassMetadata parentSelectedItemClassMetadata = ClassMetadata.ofValue(parentSelectedItem);
            parentSelectedItemClassMetadata.getProperties().stream()
                .filter(propertyMetadata -> parentItemCollectionPropertyName.equals(propertyMetadata.getName()))
                .filter(propertyMetadata -> Collection.class.isAssignableFrom(propertyMetadata.getType()))
                .filter(propertyMetadata -> propertyMetadata.getParameterizedType().getActualTypeArguments().length == 1)
                .findFirst()
                .ifPresent(propertyMetadata -> handleOneToManyRelationPropertyValues(
                    itemCreateProvider, parentSelectedItem, parentSelectedItemClassMetadata, propertyMetadata));
        }
    }

    private void handleOneToManyRelationPropertyValues(CanCreate itemCreateProvider, Object parentSelectedItem,
                                                       ClassMetadata parentSelectedItemClassMetadata, PropertyMetadata propertyMetadata) {

        Type itemType = propertyMetadata.getParameterizedType().getActualTypeArguments()[0];
        TYPE newItem = instantiateNewItem(itemType);

        if (propertyMetadata.isAnnotationPresent(OneToMany.class)) {
            String newItemJoinPropertyName = propertyMetadata.getAnnotation(OneToMany.class).mappedBy();
            if (!newItemJoinPropertyName.isEmpty()) { // else, try to find unique field that matches the type?

                PropertyMetadata newItemJoinPropertyMetadata = ClassMetadata.ofValue(newItem).getProperties().stream()
                    .filter(newItemPropertyMetadata -> newItemPropertyMetadata.getName().equals(newItemJoinPropertyName))
                    .filter(newItemPropertyMetadata -> newItemPropertyMetadata.getType().isAssignableFrom(parentSelectedItemClassMetadata.getTargetClass()))
                    .findFirst()
                    .orElseThrow(() -> new GenericItemCrudControllerException(
                        "Could not find join field in class " + itemType.getTypeName() + " by property name: " + newItemJoinPropertyName));

                newItemJoinPropertyMetadata.setValue(parentSelectedItem);
            }
        }

        itemCreateProvider.createItem(newItem);
    }

    private TYPE instantiateNewItem(Type itemType) {
        try {
            @SuppressWarnings("unchecked")
            TYPE newItem = ((Class) itemType).getConstructor().newInstance();
            return newItem;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new GenericItemCrudControllerException("Could not instantiate new item from type " + itemType.getTypeName(), e);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy