info.magnolia.ui.form.field.factory.SelectFieldFactory.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.ui.form.field.factory.SelectFieldFactory.java

Source

/**
 * This file Copyright (c) 2012-2015 Magnolia International
 * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
 *
 *
 * This file is dual-licensed under both the Magnolia
 * Network Agreement and the GNU General Public License.
 * You may elect to use one or the other of these licenses.
 *
 * This file is distributed in the hope that it will be
 * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
 * Redistribution, except as permitted by whichever of the GPL
 * or MNA you select, is prohibited.
 *
 * 1. For the GPL license (GPL), you can redistribute and/or
 * modify this file under the terms of the GNU General
 * Public License, Version 3, as published by the Free Software
 * Foundation.  You should have received a copy of the GNU
 * General Public License, Version 3 along with this program;
 * if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 2. For the Magnolia Network Agreement (MNA), this file
 * and the accompanying materials are made available under the
 * terms of the MNA which accompanies this distribution, and
 * is available at http://www.magnolia-cms.com/mna.html
 *
 * Any modifications to this file must keep this entire header
 * intact.
 *
 */
package info.magnolia.ui.form.field.factory;

import info.magnolia.jcr.util.NodeUtil;
import info.magnolia.jcr.util.SessionUtil;
import info.magnolia.ui.form.field.definition.SelectFieldDefinition;
import info.magnolia.ui.form.field.definition.SelectFieldOptionDefinition;
import info.magnolia.ui.form.field.definition.TwinColSelectFieldDefinition;
import info.magnolia.ui.vaadin.integration.jcr.DefaultPropertyUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.RepositoryException;

import org.apache.commons.collections4.ComparatorUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.commons.predicate.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.server.Resource;
import com.vaadin.server.ThemeResource;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.AbstractSelect.ItemCaptionMode;
import com.vaadin.ui.ComboBox;

/**
 * Creates and initializes a selection field based on a field definition.
 *
 * @param <D> type of definition
 */
public class SelectFieldFactory<D extends SelectFieldDefinition> extends AbstractFieldFactory<D, Object> {

    private static final Logger log = LoggerFactory.getLogger(SelectFieldFactory.class);

    private List<String> initialSelectedKey = new ArrayList<String>();
    private String optionValueName;
    private String optionLabelName;
    private final String optionIconName = SelectFieldDefinition.OPTION_ICONSRC_PROPERTY_NAME;
    private boolean hasOptionIcon = false;
    private boolean sortOptions = true;

    protected AbstractSelect select;

    public SelectFieldFactory(D definition, Item relatedFieldItem) {
        super(definition, relatedFieldItem);
    }

    @Override
    protected AbstractSelect createFieldComponent() {
        // Get name of the Label and Value property.
        optionValueName = definition.getValueProperty();
        optionLabelName = definition.getLabelProperty();
        sortOptions = definition.isSortOptions();

        select = createSelectionField();
        select.setContainerDataSource(buildOptions());
        select.setNullSelectionAllowed(false);
        select.setInvalidAllowed(false);
        select.setMultiSelect(false);
        select.setNewItemsAllowed(false);
        if (select instanceof ComboBox) {
            ((ComboBox) select).setFilteringMode(definition.getFilteringMode());
            ((ComboBox) select).setTextInputAllowed(definition.isTextInputAllowed());
            ((ComboBox) select).setPageLength(definition.getPageLength());
        }
        select.setItemCaptionMode(ItemCaptionMode.PROPERTY);
        select.setItemCaptionPropertyId(optionLabelName);

        return select;
    }

    /**
     * Used to initialize the desired subclass of AbstractSelect field component. Subclasses can override this method.
     */
    protected AbstractSelect createSelectionField() {
        return new ComboBox();
    }

    /**
     * Create a IndexContainer containing the options.
     * First element of the container is the Value.
     * Second element is the Label.
     * Third element is the Icon is defined.
     * By default, options labels are sorted alphabetically (in ascending order) unless diversely specified by {@link SelectFieldDefinition#setSortOptions(boolean)}.
     */
    @SuppressWarnings("unchecked")
    private IndexedContainer buildOptions() {
        IndexedContainer optionContainer = new IndexedContainer();

        List<SelectFieldOptionDefinition> options = getSelectFieldOptionDefinition();
        if (sortOptions) {
            if (definition.getComparatorClass() != null) {
                Comparator<SelectFieldOptionDefinition> comparator = initializeComparator(
                        definition.getComparatorClass());
                Collections.sort(options, comparator);
            } else {
                Collections.sort(options, new DefaultOptionComparator());
            }
        }
        if (!options.isEmpty()) {
            Class<?> fieldType = DefaultPropertyUtil.getFieldTypeClass(definition.getType());
            optionContainer.addContainerProperty(optionValueName, fieldType, null);
            optionContainer.addContainerProperty(optionLabelName, String.class, null);
            if (hasOptionIcon) {
                optionContainer.addContainerProperty(optionIconName, Resource.class, null);
            }
            for (SelectFieldOptionDefinition option : options) {
                Object value = DefaultPropertyUtil.createTypedValue(fieldType, option.getValue());
                Item item = optionContainer.addItem(value);
                item.getItemProperty(optionValueName).setValue(value);
                item.getItemProperty(optionLabelName).setValue(option.getLabel());
                if (StringUtils.isNotBlank(option.getIconSrc())) {
                    item.getItemProperty(optionIconName).setValue(getIconResource(option));
                }
            }
        }
        return optionContainer;
    }

    protected Comparator<SelectFieldOptionDefinition> initializeComparator(
            Class<? extends Comparator<SelectFieldOptionDefinition>> comparatorClass) {
        return getComponentProvider().newInstance(comparatorClass, item, definition, getFieldType());
    }

    /**
     * Get the list of SelectFieldOptionDefinition.
     * If options is not empty, took the options defined in this field definition.
     * Else, if path is not empty, build an options list based on the node refereed by
     * the path and property value.
     * Else nothing is define, return an empty option.
     * <b>Default value and i18n of the Label is also part of the responsibility of this method.</b>
     */
    public List<SelectFieldOptionDefinition> getSelectFieldOptionDefinition() {
        List<SelectFieldOptionDefinition> res = new ArrayList<SelectFieldOptionDefinition>();

        if (definition.getOptions() != null && !definition.getOptions().isEmpty()) {
            for (SelectFieldOptionDefinition option : definition.getOptions()) {
                option.setValue(getValue(option));
                option.setLabel(getMessage(getLabel(option)));
                if (option.isSelected()) {
                    initialSelectedKey.add(getValue(option));
                }
                if (!hasOptionIcon && StringUtils.isNotBlank(option.getIconSrc())) {
                    hasOptionIcon = true;
                }
                res.add(option);
            }
        } else if (StringUtils.isNotBlank(definition.getPath())) {
            // Build an option based on the referred node.
            buildRemoteOptions(res);
        }

        return res;
    }

    /**
     * Default Implementation to retrieve an Icon.
     * Sub class should override this method in order to retrieve
     * others Resource.
     */
    public Resource getIconResource(SelectFieldOptionDefinition option) {
        return new ThemeResource(option.getIconSrc());
    }

    /**
     * Backward compatibility.
     * If value is null, <br>
     * - get the Name as value.<br>
     * - If Name is empty, set Label as value.
     */
    private String getValue(SelectFieldOptionDefinition option) {
        if (StringUtils.isBlank(option.getValue())) {
            if (StringUtils.isNotBlank(option.getName())) {
                return option.getName();
            } else {
                return getMessage(getLabel(option));
            }
        } else {
            return option.getValue();
        }
    }

    /**
     * Backward compatibility.
     * If label is null, get the Value.
     */
    private String getLabel(SelectFieldOptionDefinition option) {
        if (StringUtils.isBlank(option.getLabel())) {
            return option.getValue();
        } else {
            return option.getLabel();
        }
    }

    /**
     * Make sure to set defaultValue whenever value is null and nullSelectionAllowed is false, i.e. not just for new node adapters.
     */
    @Override
    public void setPropertyDataSourceAndDefaultValue(Property<?> property) {
        if (!((AbstractSelect) field).isNullSelectionAllowed() && property.getValue() == null) {
            setPropertyDataSourceDefaultValue(property);
        }
        super.setPropertyDataSourceAndDefaultValue(property);
    }

    /**
     * Set the value selected.
     * Set selectedItem to the last stored value.
     * If not yet stored, set initialSelectedKey as selectedItem
     * Else, set the first element of the list.
     */
    @Override
    protected Object createDefaultValue(Property<?> dataSource) {
        Object selectedValue = null;
        Object datasourceValue = dataSource.getValue();

        if (!initialSelectedKey.isEmpty()) {
            if (select.isMultiSelect()) {
                return new HashSet(initialSelectedKey);
            }
            selectedValue = initialSelectedKey.get(0);
        } else if (!select.isNullSelectionAllowed() && select.getItemIds() != null && !select.getItemIds().isEmpty()
                && !(definition instanceof TwinColSelectFieldDefinition)) {
            selectedValue = ((AbstractSelect) field).getItemIds().iterator().next();
        }

        // Type the selected value
        selectedValue = DefaultPropertyUtil.createTypedValue(getDefinitionType(),
                selectedValue == null ? null : String.valueOf(selectedValue));
        // Set the selected value (if not null)
        if (datasourceValue != null && datasourceValue instanceof Collection && selectedValue != null) {
            ((Collection) datasourceValue).add(selectedValue);
            selectedValue = datasourceValue;
        }
        return selectedValue;
    }

    @Override
    protected Class<?> getDefinitionType() {
        Class<?> res = super.getDefinitionType();
        if (res == null) {
            res = String.class;
        }
        return res;
    }

    /**
     * Build options based on a remote Node.
     * Simply get the remote Node, Iterate his child nodes and for every child
     * try to get the Value and Label property.
     * In addition create an ArrayList<SelectFieldOptionDefinition> representing this options.
     */
    private void buildRemoteOptions(List<SelectFieldOptionDefinition> res) {
        Node parent = SessionUtil.getNode(definition.getRepository(), definition.getPath());
        if (parent != null) {
            try {
                // Get only relevant child nodes
                Iterable<Node> iterable = NodeUtil.getNodes(parent, createRemoteOptionFilterPredicate());
                Iterator<Node> iterator = iterable.iterator();
                // Iterate parent children
                while (iterator.hasNext()) {
                    SelectFieldOptionDefinition option = new SelectFieldOptionDefinition();
                    Node child = iterator.next();
                    // Get Label and Value
                    String label = getRemoteOptionsName(child, optionLabelName);
                    String value = getRemoteOptionsValue(child, optionValueName);
                    option.setLabel(getMessage(label));
                    option.setValue(value);

                    if (child.hasProperty(SelectFieldDefinition.OPTION_SELECTED_PROPERTY_NAME) && Boolean
                            .parseBoolean(child.getProperty(SelectFieldDefinition.OPTION_SELECTED_PROPERTY_NAME)
                                    .getString())) {
                        option.setSelected(true);
                        initialSelectedKey.add(option.getValue());
                    }
                    if (child.hasProperty(SelectFieldDefinition.OPTION_NAME_PROPERTY_NAME)) {
                        option.setName(
                                child.getProperty(SelectFieldDefinition.OPTION_NAME_PROPERTY_NAME).getString());
                    }
                    if (child.hasProperty(SelectFieldDefinition.OPTION_ICONSRC_PROPERTY_NAME)) {
                        option.setIconSrc(
                                child.getProperty(SelectFieldDefinition.OPTION_ICONSRC_PROPERTY_NAME).getString());
                        hasOptionIcon = true;
                    }
                    res.add(option);
                }
                definition.setOptions(res);
            } catch (Exception e) {
                log.warn("Not able to build options based on option node " + parent.toString(), e);
            }
        }
    }

    /**
     * @return {@link Predicate} used to filter the remote children option nodes.
     */
    protected Predicate createRemoteOptionFilterPredicate() {
        return NodeUtil.MAGNOLIA_FILTER;
    }

    /**
     * Get the specific node property. <br>
     * If this property is not defined, return the node name.
     * Expose this method in order to let subclass define their own implementation.
     */
    protected String getRemoteOptionsName(Node option, String propertyName) throws RepositoryException {
        if (option.hasProperty(propertyName)) {
            return option.getProperty(propertyName).getString();
        } else {
            return option.getName();
        }
    }

    /**
     * Get the specific node property. <br>
     * If this property is not defined, return the node name.
     * Expose this method in order to let subclass define their own implementation.
     */
    protected String getRemoteOptionsValue(Node option, String propertyName) throws RepositoryException {
        return getRemoteOptionsName(option, propertyName);
    }

    /**
     * A null safe comparator based on the label.
     */
    public static class DefaultOptionComparator implements Comparator<SelectFieldOptionDefinition> {

        @Override
        public int compare(SelectFieldOptionDefinition def, SelectFieldOptionDefinition otherDef) {
            // Null safe comparison of Comparables. null is assumed to be less than a non-null value.
            return ComparatorUtils.nullLowComparator(String.CASE_INSENSITIVE_ORDER).compare(def.getLabel(),
                    otherDef.getLabel());
        }
    }
}