org.opensingular.form.wicket.WicketBuildContext.java Source code

Java tutorial

Introduction

Here is the source code for org.opensingular.form.wicket.WicketBuildContext.java

Source

/*
 * Copyright (C) 2016 Singular Studios (a.k.a Atom Tecnologia) - www.opensingular.com
 *
 * 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 org.opensingular.form.wicket;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.model.IModel;
import org.apache.wicket.util.string.Strings;
import org.opensingular.form.SInstance;
import org.opensingular.form.document.SDocument;
import org.opensingular.form.view.SView;
import org.opensingular.form.view.ViewResolver;
import org.opensingular.form.wicket.IWicketComponentMapper.HintKey;
import org.opensingular.form.wicket.behavior.ConfigureByMInstanciaAttributesBehavior;
import org.opensingular.form.wicket.enums.AnnotationMode;
import org.opensingular.form.wicket.enums.ViewMode;
import org.opensingular.form.wicket.feedback.AbstractSValidationFeedbackPanel;
import org.opensingular.form.wicket.feedback.FeedbackFence;
import org.opensingular.form.wicket.feedback.SValidationFeedbackCompactPanel;
import org.opensingular.form.wicket.feedback.SValidationFeedbackPanel;
import org.opensingular.form.wicket.mapper.ListBreadcrumbMapper;
import org.opensingular.form.wicket.mapper.TabMapper;
import org.opensingular.form.wicket.model.ISInstanceAwareModel;
import org.opensingular.form.wicket.model.SInstanceFieldModel;
import org.opensingular.form.wicket.model.SInstanceValueModel;
import org.opensingular.form.wicket.util.WicketFormProcessing;
import org.opensingular.form.wicket.util.WicketFormUtils;
import org.opensingular.lib.commons.lambda.ISupplier;
import org.opensingular.lib.wicket.util.bootstrap.layout.BSCol;
import org.opensingular.lib.wicket.util.bootstrap.layout.BSContainer;
import org.opensingular.lib.wicket.util.bootstrap.layout.IBSComponentFactory;
import org.opensingular.lib.wicket.util.model.IReadOnlyModel;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;

@SuppressWarnings("serial")
public class WicketBuildContext implements Serializable {

    static final HintKey<HashMap<String, Integer>> COL_WIDTHS = () -> new HashMap<>();

    public static final MetaDataKey<WicketBuildContext> METADATA_KEY = new MetaDataKey<WicketBuildContext>() {
    };

    public static final HintKey<IModel<String>> TITLE_KEY = () -> null;
    public static final HintKey<Boolean> RECEIVES_INVISIBLE_INNER_COMPONENT_ERRORS_KEY = () -> null;

    private final List<WicketBuildContext> children = newArrayList();
    private final HashMap<HintKey<?>, Serializable> hints = new HashMap<>();

    private final WicketBuildContext parent;
    private final BSContainer<?> container;
    private final boolean hintsInherited;
    private final BSContainer<?> externalContainer;

    private IModel<? extends SInstance> model;
    private UIBuilderWicket uiBuilderWicket;
    private ViewMode viewMode;

    private AnnotationMode annotation = AnnotationMode.NONE;

    private boolean showBreadcrumb;
    private boolean nested = false;
    private boolean titleInBlock = false;
    private List<String> breadCrumbs = newArrayList();
    private Deque<ListBreadcrumbMapper.BreadCrumbPanel.BreadCrumbStatus> breadCrumbStatus = newLinkedList();
    private ListBreadcrumbMapper.BreadCrumbPanel.BreadCrumbStatus selectedBreadCrumbStatus;

    private IBSComponentFactory<Component> preFormPanelFactory;

    private SView view;

    public WicketBuildContext(BSCol container, BSContainer<?> externalContainer,
            IModel<? extends SInstance> model) {
        this(null, container, externalContainer, false, model);
    }

    protected WicketBuildContext(WicketBuildContext parent, BSContainer<?> container,
            BSContainer<?> externalContainer, boolean hintsInherited, IModel<? extends SInstance> model) {

        this.parent = parent;
        if (parent != null) {
            parent.children.add(this);
        }
        this.container = container;
        this.hintsInherited = hintsInherited;
        this.externalContainer = externalContainer;
        this.model = model;
        WicketFormUtils.markAsCellContainer(container);
        container.add(ConfigureByMInstanciaAttributesBehavior.getInstance());
        container.setMetaData(METADATA_KEY, this);
    }

    public WicketBuildContext createChild(BSContainer<?> childContainer, boolean hintsInherited,
            IModel<? extends SInstance> model) {
        return configureNestedContext(
                new WicketBuildContext(this, childContainer, getExternalContainer(), hintsInherited, model)
                        .setAnnotationMode(getAnnotationMode()));
    }

    public WicketBuildContext createChild(BSContainer<?> childContainer, BSContainer<?> externalContainer,
            boolean hintsInherited, IModel<? extends SInstance> model) {
        return configureNestedContext(
                new WicketBuildContext(this, childContainer, externalContainer, hintsInherited, model)
                        .setAnnotationMode(getAnnotationMode()));
    }

    private WicketBuildContext configureNestedContext(WicketBuildContext context) {
        context.setNested(nested);
        return context;
    }

    public WicketBuildContext init(UIBuilderWicket uiBuilderWicket, ViewMode viewMode) {

        final SInstance instance = getCurrentInstance();

        this.view = ViewResolver.resolve(instance);
        this.uiBuilderWicket = uiBuilderWicket;
        this.viewMode = viewMode;

        if (isRootContext()) {
            initContainerBehavior();
        }

        if (getContainer().getDefaultModel() == null) {
            getContainer().setDefaultModel(getModel());
        }

        WicketFormUtils.setInstanceId(getContainer(), instance);
        WicketFormUtils.setRootContainer(getContainer(), getRootContainer());

        return this;
    }

    public AnnotationMode getAnnotationMode() {
        return annotation;
    }

    public WicketBuildContext setAnnotationMode(AnnotationMode mode) {
        Objects.requireNonNull(mode);
        annotation = mode;
        return this;
    }

    /**
     * Adiciona um behavior que executa o update atributes do SDocument em toda requisio.
     * <p>
     * Normalmente este mtodo no deve ser chamado externamente,
     * porem pode existir situaes em que o container root no  atualizado
     * e novos componentes filhos so adicionados.
     *
     * @see SDocument
     * @see TabMapper
     */
    public void initContainerBehavior() {
        getContainer().add(new InitRootContainerBehavior(getModel()));
    }

    /**
     * Configura formComponentes, adicionando comportamentos de acordo com sua definio.
     *
     * @param mapper        o mapper
     * @param formComponent o componente que tem como model IMInstanciaAwareModel
     */
    public <C extends FormComponent<?>> C configure(IWicketComponentMapper mapper, C formComponent) {
        final IModel<?> defaultModel = formComponent.getDefaultModel();
        if (defaultModel != null && ISInstanceAwareModel.class.isAssignableFrom(defaultModel.getClass())) {
            WicketFormUtils.setCellContainer(formComponent, getContainer());
            formComponent.add(ConfigureByMInstanciaAttributesBehavior.getInstance());
            if (formComponent.getLabel() == null) {
                // formComponent.setDescription(IReadOnlyModel.of(() -> resolveSimpleLabel(formComponent)));
                formComponent.setLabel(IReadOnlyModel.of(() -> resolveFullPathLabel(formComponent)));
            }
            ISInstanceAwareModel<?> selectedModel = (ISInstanceAwareModel<?>) defaultModel;
            // final SType<?> tipo = selectedModel.getSInstance().getType();
            // if (tipo.hasDependentTypes() || tipo.dependsOnAnyTypeInHierarchy())
            mapper.addAjaxUpdate(formComponent, ISInstanceAwareModel.getInstanceModel(selectedModel),
                    new OnFieldUpdatedListener());
        }
        return formComponent;
    }

    public void configureContainer(IModel<String> title) {
        setHint(TITLE_KEY, title);
    }

    public Optional<IModel<String>> resolveContainerTitle() {
        return Optional.ofNullable(getHint(TITLE_KEY));
    }

    //    public boolean resolveReceivesInvisibleInnerComponentErrors() {
    //        return Boolean.TRUE.equals(getHint(RECEIVES_INVISIBLE_INNER_COMPONENT_ERRORS_KEY));
    //    }

    public static Optional<WicketBuildContext> find(Component comp) {
        return Optional.ofNullable(comp.getMetaData(METADATA_KEY));
    }

    public static Optional<WicketBuildContext> findNearest(Component comp) {
        for (Component c = comp; c != null; c = c.getParent()) {
            Optional<WicketBuildContext> ctx = find(c);
            if (ctx.isPresent()) {
                return ctx;
            }
        }
        return Optional.empty();
    }

    public static Stream<WicketBuildContext> streamParentContexts(Component comp) {
        return findNearest(comp).map(ctx -> ctx.streamParentContexts()).orElse(Stream.empty());
    }

    public Stream<WicketBuildContext> streamParentContexts() {
        final Builder<WicketBuildContext> sb = Stream.builder();
        for (WicketBuildContext ctx = this; ctx != null; ctx = ctx.getParent())
            sb.add(ctx);
        return sb.build();
    }

    public static Optional<WicketBuildContext> findTopLevel(Component comp) {
        return findNearest(comp).map(it -> it.getRootContext());
    }

    protected static String resolveSimpleLabel(FormComponent<?> formComponent) {
        IModel<?> model = formComponent.getModel();
        if (model instanceof ISInstanceAwareModel<?>) {
            SInstance instancia = ((ISInstanceAwareModel<?>) model).getSInstance();
            return instancia.asAtr().getLabel();
        }
        return "[" + formComponent.getId() + "]";
    }

    /**
     * Calcula o caminho completo de labels do campo, concatenando os nomes separados por ' > ',
     * para ser usado em mensagens de erro.
     * Exemplo: "O campo 'Contato > Endereos > Endereo > Logradouro'  obrigatrio"
     */
    protected static String resolveFullPathLabel(FormComponent<?> formComponent) {
        IModel<?> model = formComponent.getModel();
        if (model instanceof ISInstanceAwareModel<?>) {
            SInstance instancia = ((ISInstanceAwareModel<?>) model).getSInstance();
            List<String> labels = new ArrayList<>();
            while (instancia != null) {
                labels.add(instancia.asAtr().getLabel());
                instancia = instancia.getParent();
            }
            labels.removeIf(it -> Strings.defaultIfEmpty(it, "").trim().isEmpty());
            Collections.reverse(labels);
            if (!labels.isEmpty())
                return Strings.join(" > ", labels);
        }
        return "[" + formComponent.getId() + "]";
    }

    public WicketBuildContext getRootContext() {
        WicketBuildContext ctx = this;
        while (!ctx.isRootContext())
            ctx = ctx.getParent();
        return ctx;
    }

    /**
     * @return true if this is the root of a Context tree.
     */
    public boolean isRootContext() {
        return (this.getParent() == null);
    }

    public BSContainer<?> getRootContainer() {
        return getRootContext().getContainer();
    }

    public WicketBuildContext getParent() {
        return parent;
    }

    public List<WicketBuildContext> getChildren() {
        return newArrayList(children);
    }

    public BSContainer<?> getContainer() {
        return container;
    }

    public SValidationFeedbackPanel createFeedbackPanel(String id) {
        return createFeedbackPanel(id, ISValidationFeedbackHandlerListener::refresh, getContainer());
    }

    public SValidationFeedbackPanel createFeedbackPanel(String id, MarkupContainer container) {
        return createFeedbackPanel(id, ISValidationFeedbackHandlerListener::refresh, container);
    }

    public SValidationFeedbackPanel createFeedbackPanel(String id,
            Function<Component, ISValidationFeedbackHandlerListener> listenerFunc, MarkupContainer container) {
        return createFeedbackPanel(() -> new SValidationFeedbackPanel(id, new FeedbackFence(container)),
                listenerFunc);
    }

    public SValidationFeedbackCompactPanel createFeedbackCompactPanel(String id) {
        return createFeedbackCompactPanel(id, ISValidationFeedbackHandlerListener::refresh);
    }

    public SValidationFeedbackCompactPanel createFeedbackCompactPanel(String id,
            Function<Component, ISValidationFeedbackHandlerListener> listenerFunc) {
        return createFeedbackPanel(() -> new SValidationFeedbackCompactPanel(id,
                new FeedbackFence(getContainer(), getExternalContainer())), listenerFunc);
    }

    private <C extends AbstractSValidationFeedbackPanel> C createFeedbackPanel(ISupplier<C> factory,
            Function<Component, ISValidationFeedbackHandlerListener> listenerFunc) {
        C feedback = factory.get();
        SValidationFeedbackHandler handler = SValidationFeedbackHandler.bindTo(feedback.getFence())
                .addInstanceModel(getModel());
        if (listenerFunc != null) {
            ISValidationFeedbackHandlerListener listener = listenerFunc.apply(feedback);
            if (listener != null)
                handler.addListener(listener);
        }
        return feedback;
    }

    public BSContainer<?> getExternalContainer() {
        return externalContainer;
    }

    public <T extends Serializable> WicketBuildContext setHint(HintKey<T> key, T value) {
        hints.put(key, value);
        return this;
    }

    @SuppressWarnings("unchecked")
    public <T> T getHint(HintKey<T> key) {
        if (hints.containsKey(key)) {
            return (T) hints.get(key);
        } else if (hintsInherited && getParent() != null) {
            return getParent().getHint(key);
        } else {
            return key.getDefaultValue();
        }
    }

    public void rebuild(List<String> nomesTipo) {
        IModel<? extends SInstance> originalModel = getModel();
        for (String nomeTipo : nomesTipo) {
            SInstanceFieldModel<SInstance> subtree = new SInstanceFieldModel<>(originalModel, nomeTipo);
            setModel(subtree);
            getUiBuilderWicket().build(this, viewMode);
        }

        setModel(originalModel);

    }

    public void popBreadCrumb() {
        getBreadCrumbs().remove(getBreadCrumbs().size() - 1);
    }

    public void updateExternalContainer(AjaxRequestTarget ajaxRequestTarget) {
        if (ajaxRequestTarget != null) {
            ajaxRequestTarget.add(this.getExternalContainer());
        }
    }

    private static final class InitRootContainerBehavior extends Behavior {

        private final IModel<? extends SInstance> instanceModel;

        public InitRootContainerBehavior(IModel<? extends SInstance> instanceModel) {
            this.instanceModel = instanceModel;
        }

        @Override
        public void onConfigure(Component component) {
            if (instanceModel.getObject() != null) {
                instanceModel.getObject().getDocument().updateAttributes(null);
            }
        }
    }

    public static final class OnFieldUpdatedListener implements IAjaxUpdateListener {

        private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(OnFieldUpdatedListener.class);

        @Override
        public void onValidate(Component s, AjaxRequestTarget t, IModel<? extends SInstance> m) {
            WicketFormProcessing.onFieldValidate((FormComponent<?>) s, t, m);
        }

        @Override
        public void onProcess(Component s, AjaxRequestTarget t, IModel<? extends SInstance> m) {
            long ms = Calendar.getInstance().getTimeInMillis();
            WicketFormProcessing.onFieldProcess(s, t, m);
            LOGGER.info("[SINGULAR] Tempo processando (ms): {}", (Calendar.getInstance().getTimeInMillis() - ms));
        }

        @Override
        public void onError(Component source, AjaxRequestTarget target, IModel<? extends SInstance> instanceModel) {
            WicketFormProcessing.onFormError((FormComponent<?>) source, target);
        }
    }

    public UIBuilderWicket getUiBuilderWicket() {
        return (uiBuilderWicket != null) ? uiBuilderWicket : getParent().getUiBuilderWicket();
    }

    public ViewMode getViewMode() {
        return (viewMode != null) ? viewMode : getParent().getViewMode();
    }

    public SView getView() {
        return view;
    }

    public IModel<? extends SInstance> getModel() {
        return model;
    }

    public IModel<?> getValueModel() {
        return new SInstanceValueModel<>(getModel());
    }

    public void setModel(IModel<? extends SInstance> model) {
        this.model = model;
    }

    public boolean isShowBreadcrumb() {
        return showBreadcrumb;
    }

    public void setShowBreadcrumb(boolean showBreadcrumb) {
        this.showBreadcrumb = showBreadcrumb;
    }

    public List<String> getBreadCrumbs() {
        if (isRootContext()) {
            return breadCrumbs;
        }
        return getRootContext().getBreadCrumbs();
    }

    public Deque<ListBreadcrumbMapper.BreadCrumbPanel.BreadCrumbStatus> getBreadCrumbStatus() {
        if (isRootContext()) {
            return breadCrumbStatus;
        }
        return getRootContext().getBreadCrumbStatus();
    }

    public ListBreadcrumbMapper.BreadCrumbPanel.BreadCrumbStatus getSelectedBreadCrumbStatus() {
        return selectedBreadCrumbStatus;
    }

    public void setSelectedBreadCrumbStatus(
            ListBreadcrumbMapper.BreadCrumbPanel.BreadCrumbStatus selectedBreadCrumbStatus) {
        this.selectedBreadCrumbStatus = selectedBreadCrumbStatus;
    }

    @SuppressWarnings("unchecked")
    public <T extends SInstance> T getCurrentInstance() {
        return (T) getModel().getObject();
    }

    public boolean isTitleInBlock() {
        return titleInBlock;
    }

    public void setTitleInBlock(boolean titleInBlock) {
        this.titleInBlock = titleInBlock;
    }

    public boolean isNested() {
        return nested;
    }

    public void setNested(boolean nested) {
        this.nested = nested;
    }

    public IBSComponentFactory<Component> getPreFormPanelFactory() {
        return preFormPanelFactory;
    }

    public void setPreFormPanelFactory(IBSComponentFactory<Component> preFormPanelFactory) {
        this.preFormPanelFactory = preFormPanelFactory;
    }

}