com.qcadoo.view.internal.components.grid.GridComponentPattern.java Source code

Java tutorial

Introduction

Here is the source code for com.qcadoo.view.internal.components.grid.GridComponentPattern.java

Source

/**
 * ***************************************************************************
 * Copyright (c) 2010 Qcadoo Limited
 * Project: Qcadoo Framework
 * Version: 1.2.0
 *
 * This file is part of Qcadoo.
 *
 * Qcadoo is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * ***************************************************************************
 */
package com.qcadoo.view.internal.components.grid;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;
import com.qcadoo.model.api.DataDefinition;
import com.qcadoo.model.api.FieldDefinition;
import com.qcadoo.model.api.types.DataDefinitionHolder;
import com.qcadoo.model.api.types.EnumeratedType;
import com.qcadoo.model.api.types.FieldType;
import com.qcadoo.model.api.types.JoinFieldHolder;
import com.qcadoo.view.api.ComponentState;
import com.qcadoo.view.internal.ComponentDefinition;
import com.qcadoo.view.internal.ComponentOption;
import com.qcadoo.view.internal.CriteriaModifier;
import com.qcadoo.view.internal.RowStyleResolver;
import com.qcadoo.view.internal.patterns.AbstractComponentPattern;
import com.qcadoo.view.internal.xml.ViewDefinitionParser;
import com.qcadoo.view.internal.xml.ViewDefinitionParserNodeException;

public class GridComponentPattern extends AbstractComponentPattern {

    private static final String L_COLUMN = "column";

    private static final String L_WIDTH = "width";

    private static final String JSP_PATH = "elements/grid.jsp";

    private static final String JS_OBJECT = "QCD.components.elements.Grid";

    private static final int DEFAULT_GRID_HEIGHT = 300;

    private static final int DEFAULT_GRID_WIDTH = 300;

    private static final Predicate<GridComponentColumn> COLUMNS_VISIBLE_FOR_TENANT_PREDICATE = new Predicate<GridComponentColumn>() {

        @Override
        public boolean apply(final GridComponentColumn column) {
            return column != null && column.isVisibleForCurrentTenant();
        }
    };

    private static final Function<Entry<String, GridComponentColumn>, GridComponentColumn> VALUE_FROM_MAP_ENTRY_FUNCTION = new Function<Entry<String, GridComponentColumn>, GridComponentColumn>() {

        @Override
        public GridComponentColumn apply(final Entry<String, GridComponentColumn> from) {
            if (from == null) {
                return null;
            } else {
                return from.getValue();
            }
        }
    };

    private final Set<String> searchableColumns = new HashSet<String>();

    private final Set<String> orderableColumns = new HashSet<String>();

    private final Map<String, GridComponentColumn> columns = new LinkedHashMap<String, GridComponentColumn>();

    private FieldDefinition belongsToFieldDefinition;

    private String correspondingView;

    private String correspondingComponent;

    private String correspondingLookup;

    private boolean correspondingViewInModal = false;

    private boolean paginable = true;

    private boolean deletable = false;

    private boolean creatable = false;

    private boolean multiselect = false;

    private boolean hasPredefinedFilters = false;

    private boolean filtersDefaultVisible = true;

    private boolean weakRelation = false;

    private String defaultPredefinedFilterName = "";

    private final Map<String, PredefinedFilter> predefinedFilters = Maps.newLinkedHashMap();

    private int height = DEFAULT_GRID_HEIGHT;

    private int width = DEFAULT_GRID_WIDTH;

    private String defaultOrderColumn;

    private String defaultOrderDirection;

    private boolean lookup = false;

    private boolean activable = false;

    private Boolean fixedHeight;

    private RowStyleResolver rowStyleResolver = null;

    private CriteriaModifier criteriaModifier = null;

    public GridComponentPattern(final ComponentDefinition componentDefinition) {
        super(componentDefinition);
    }

    @Override
    public ComponentState getComponentStateInstance() {
        DataDefinition scopeFieldDataDefinition = null;
        if (getScopeFieldDefinition() != null) {
            scopeFieldDataDefinition = getScopeFieldDefinition().getDataDefinition();
        }
        return new GridComponentState(scopeFieldDataDefinition, this);
    }

    @Override
    public String getJspFilePath() {
        return JSP_PATH;
    }

    @Override
    public String getJsFilePath() {
        return JS_PATH;
    }

    @Override
    public String getJsObjectName() {
        return JS_OBJECT;
    }

    @Override
    protected void initializeComponent() throws JSONException {
        configureBelongsToFieldDefinition();
        parseOptions();

        activable = getDataDefinition().isActivable();

        if (creatable && weakRelation && getScopeFieldDefinition() == null) {
            throwIllegalStateException("Missing scope field for grid");
        }

        if (correspondingView != null && correspondingComponent == null) {
            throwIllegalStateException("Missing correspondingComponent for grid");
        }

        if (weakRelation && creatable && correspondingLookup == null) {
            throwIllegalStateException("Missing correspondingLookup for grid");
        }
    }

    private void configureBelongsToFieldDefinition() {
        if (getScopeFieldDefinition() != null) {
            FieldType fieldType = getScopeFieldDefinition().getType();
            if (fieldType instanceof JoinFieldHolder && fieldType instanceof DataDefinitionHolder) {
                belongsToFieldDefinition = ((DataDefinitionHolder) fieldType).getDataDefinition()
                        .getField(((JoinFieldHolder) fieldType).getJoinFieldName());
            } else {
                throwIllegalStateException("Scope field for grid should be one of: hasMany, tree or manyToMany");
            }
        }
    }

    @Override
    protected JSONObject getJsOptions(final Locale locale) throws JSONException {
        JSONObject json = super.getJsOptions(locale);
        json.put("paginable", paginable);
        json.put("deletable", deletable);
        json.put("creatable", creatable);
        json.put("multiselect", multiselect);
        json.put("activable", activable);
        json.put("weakRelation", weakRelation);

        json.put("hasPredefinedFilters", hasPredefinedFilters);
        json.put("filtersDefaultVisible", filtersDefaultVisible);

        JSONArray predefinedFiltersArray = new JSONArray();
        for (PredefinedFilter predefinedFilter : predefinedFilters.values()) {
            predefinedFiltersArray.put(predefinedFilter.toJson());
        }

        json.put("predefinedFilters", predefinedFiltersArray);

        json.put("height", height);
        json.put(L_WIDTH, width);
        json.put("fullscreen", width == 0 || height == 0);
        json.put("lookup", lookup);
        json.put("correspondingView", correspondingView);
        json.put("correspondingComponent", correspondingComponent);
        json.put("correspondingLookup", correspondingLookup);
        json.put("correspondingViewInModal", correspondingViewInModal);
        json.put("prioritizable", getDataDefinition().isPrioritizable());
        json.put("searchableColumns", new JSONArray(searchableColumns));
        json.put("orderableColumns", new JSONArray(orderableColumns));

        json.put("fixedHeight", fixedHeight);

        if (belongsToFieldDefinition != null) {
            json.put("belongsToFieldName", belongsToFieldDefinition.getName());
        }

        json.put("columns", getColumnsForJsOptions(locale));

        JSONObject translations = new JSONObject();

        addTranslation(translations, "unactiveVisibleButton", locale);
        addTranslation(translations, "unactiveNotVisibleButton", locale);
        addTranslation(translations, "addFilterButton", locale);
        addTranslation(translations, "clearFilterButton", locale);
        addTranslation(translations, "noResults", locale);
        addTranslation(translations, "removeFilterButton", locale);
        addTranslation(translations, "newButton", locale);
        addTranslation(translations, "addExistingButton", locale);
        addTranslation(translations, "deleteButton", locale);
        addTranslation(translations, "upButton", locale);
        addTranslation(translations, "downButton", locale);
        addTranslation(translations, "perPage", locale);
        addTranslation(translations, "outOfPages", locale);
        addTranslation(translations, "noRowSelectedError", locale);
        addTranslation(translations, "confirmDeleteMessage", locale);
        addTranslation(translations, "wrongSearchCharacterError", locale);
        addTranslation(translations, "header", locale);
        addTranslation(translations, "selectAll", locale);
        addTranslation(translations, "diselectAll", locale);

        addTranslation(translations, "customPredefinedFilter", locale);
        for (PredefinedFilter filter : predefinedFilters.values()) {
            addTranslation(translations, "filter." + filter.getName(), locale);
        }

        translations.put("loading", getTranslationService().translate("qcadooView.loading", locale));

        json.put("translations", translations);

        return json;
    }

    public void addColumn(final String name, final String fields, final String expression, final Boolean isLink,
            final Integer width, final boolean isOrderable, final boolean isSearchable) {
        addColumn(name, fields, expression, isLink, width, isOrderable, isSearchable, null);
    }

    public void addColumn(final String name, final String fields, final String expression, final Boolean isLink,
            final Integer width, final boolean isOrderable, final boolean isSearchable,
            final String extendingPluginIdentifier) {
        final GridComponentColumn column = new GridComponentColumn(name, extendingPluginIdentifier);
        for (FieldDefinition field : parseFields(fields, column)) {
            column.addField(field);
        }
        column.setExpression(expression);
        if (isLink != null) {
            column.setLink(isLink);
        }
        if (width != null) {
            column.setWidth(width);
        }
        columns.put(name, column);
        if (isOrderable) {
            orderableColumns.add(name);
        }
        if (isSearchable) {
            searchableColumns.add(name);
        }
    }

    public void removeColumn(final String name) {
        columns.remove(name);
        orderableColumns.remove(name);
        searchableColumns.remove(name);
    }

    private void addTranslation(final JSONObject translation, final String key, final Locale locale)
            throws JSONException {
        translation.put(key, getTranslationService().translate(getTranslationPath() + "." + key,
                "qcadooView.grid." + key, locale));
    }

    private JSONArray getColumnsForJsOptions(final Locale locale) throws JSONException {
        JSONArray jsonColumns = new JSONArray();
        String nameTranslation = null;
        for (GridComponentColumn column : columns.values()) {
            if (!COLUMNS_VISIBLE_FOR_TENANT_PREDICATE.apply(column)) {
                continue;
            }
            if (column.getFields().size() == 1) {
                String fieldCode = getDataDefinition().getPluginIdentifier() + "." + getDataDefinition().getName()
                        + "." + column.getFields().get(0).getName();
                nameTranslation = getTranslationService().translate(
                        getTranslationPath() + ".column." + column.getName(), fieldCode + ".label", locale);
            } else {
                nameTranslation = getTranslationService()
                        .translate(getTranslationPath() + ".column." + column.getName(), locale);
            }
            JSONObject jsonColumn = new JSONObject();
            jsonColumn.put("name", column.getName());
            jsonColumn.put("label", nameTranslation);
            jsonColumn.put("link", column.isLink());
            jsonColumn.put("hidden", column.isHidden());
            jsonColumn.put(L_WIDTH, column.getWidth());
            jsonColumn.put("align", column.getAlign());
            jsonColumn.put("filterValues", getFilterValuesForColumn(column, locale));
            jsonColumns.put(jsonColumn);
        }

        return jsonColumns;
    }

    public JSONObject getFilterValuesForColumn(final GridComponentColumn column, final Locale locale)
            throws JSONException {
        if (column.getFields().size() != 1) {
            return null;
        }

        JSONObject json = new JSONObject();

        if (column.getFields().get(0).getType() instanceof EnumeratedType) {
            EnumeratedType type = (EnumeratedType) column.getFields().get(0).getType();
            Map<String, String> sortedValues = type.values(locale);
            for (Map.Entry<String, String> value : sortedValues.entrySet()) {
                json.put(value.getKey(), value.getValue());
            }
        } else if (column.getFields().get(0).getType().getType().equals(Boolean.class)) {
            json.put("1", getTranslationService().translate("qcadooView.true", locale));
            json.put("0", getTranslationService().translate("qcadooView.false", locale));
        }

        if (json.length() > 0) {
            return json;
        } else {
            return null;
        }

    }

    @Override
    public void parse(final Node componentNode, final ViewDefinitionParser parser)
            throws ViewDefinitionParserNodeException {
        super.parse(componentNode, parser);
        final NodeList childNodes = componentNode.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            final Node child = childNodes.item(i);
            if ("predefinedFilters".equals(child.getNodeName())) {
                String defaultValue = parser.getStringAttribute(child, "default");
                if (defaultValue != null) {
                    defaultPredefinedFilterName = defaultValue;
                }
                NodeList predefinedFilterChildNodes = child.getChildNodes();
                parsePredefinedFilterChildNodes(predefinedFilterChildNodes, parser);
            } else if (RowStyleResolver.NODE_NAME.equals(child.getNodeName())) {
                rowStyleResolver = new RowStyleResolver(child, parser, getApplicationContext());
            } else if (CriteriaModifier.NODE_NAME.equals(child.getNodeName())) {
                criteriaModifier = new CriteriaModifier(child, parser, getApplicationContext());
            }
        }

    }

    private void parsePredefinedFilterChildNodes(final NodeList componentNodes, final ViewDefinitionParser parser)
            throws ViewDefinitionParserNodeException {
        for (int i = 0; i < componentNodes.getLength(); i++) {
            Node child = componentNodes.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                if ("predefinedFilter".equals(child.getNodeName())) {
                    PredefinedFilter predefinedFilter = new PredefinedFilter();
                    String predefinedFilterName = parser.getStringAttribute(child, "name");
                    predefinedFilter.setName(predefinedFilterName);

                    NodeList restrictionNodes = child.getChildNodes();
                    for (int restrictionNodesIndex = 0; restrictionNodesIndex < restrictionNodes
                            .getLength(); restrictionNodesIndex++) {
                        Node restrictionNode = restrictionNodes.item(restrictionNodesIndex);
                        if (restrictionNode.getNodeType() == Node.ELEMENT_NODE) {
                            if ("filterRestriction".equals(restrictionNode.getNodeName())) {
                                predefinedFilter.addFilterRestriction(
                                        parser.getStringAttribute(restrictionNode, L_COLUMN),
                                        parser.getStringAttribute(restrictionNode, "value"));
                            } else if ("filterOrder".equals(restrictionNode.getNodeName())) {
                                String column = parser.getStringAttribute(restrictionNode, L_COLUMN);
                                String direction = parser.getStringAttribute(restrictionNode, "direction");
                                if (column == null) {
                                    throw new ViewDefinitionParserNodeException(restrictionNode,
                                            "'filterOrder' tag must contain 'column' attribute");
                                }
                                if (direction == null) {
                                    direction = "asc";
                                } else {
                                    if (!("asc".equals(direction) || "desc".equals(direction))) {
                                        throw new ViewDefinitionParserNodeException(restrictionNode,
                                                "unknown order direction: " + direction);
                                    }
                                }
                                predefinedFilter.setOrderColumn(column);
                                predefinedFilter.setOrderDirection(direction);
                            } else {
                                throw new ViewDefinitionParserNodeException(restrictionNode,
                                        "predefinedFilter can only contain 'filterRestriction' or 'filterOrder' tag");
                            }
                        }
                    }

                    if (predefinedFilter.getOrderColumn() == null && defaultOrderColumn != null) {
                        predefinedFilter.setOrderColumn(defaultOrderColumn);
                        predefinedFilter.setOrderDirection(defaultOrderDirection);
                    }

                    predefinedFilters.put(predefinedFilterName, predefinedFilter);
                } else {
                    throwIllegalStateException("predefinedFilters can only contain 'predefinedFilter' tag");
                    throw new ViewDefinitionParserNodeException(child,
                            "predefinedFilters can only contain 'predefinedFilter' tag");
                }
            }
        }
    }

    private void parseOptions() {
        for (ComponentOption option : getOptions()) {
            if ("correspondingView".equals(option.getType())) {
                correspondingView = option.getValue();
            } else if ("correspondingComponent".equals(option.getType())) {
                correspondingComponent = option.getValue();
            } else if ("correspondingLookup".equals(option.getType())) {
                correspondingLookup = option.getValue();
            } else if ("correspondingViewInModal".equals(option.getType())) {
                correspondingViewInModal = Boolean.parseBoolean(option.getValue());
            } else if ("paginable".equals(option.getType())) {
                paginable = Boolean.parseBoolean(option.getValue());
            } else if ("creatable".equals(option.getType())) {
                creatable = Boolean.parseBoolean(option.getValue());
            } else if ("multiselect".equals(option.getType())) {
                multiselect = Boolean.parseBoolean(option.getValue());
            } else if ("hasPredefinedFilters".equals(option.getType())) {
                hasPredefinedFilters = Boolean.parseBoolean(option.getValue());
            } else if ("filtersDefaultVisible".equals(option.getType())) {
                filtersDefaultVisible = Boolean.parseBoolean(option.getValue());
            } else if ("deletable".equals(option.getType())) {
                deletable = Boolean.parseBoolean(option.getValue());
            } else if ("height".equals(option.getType())) {
                height = Integer.parseInt(option.getValue());
            } else if (L_WIDTH.equals(option.getType())) {
                width = Integer.parseInt(option.getValue());
            } else if ("fullscreen".equals(option.getType())) {
                width = 0;
                height = 0;
            } else if ("lookup".equals(option.getType())) {
                lookup = Boolean.parseBoolean(option.getValue());
            } else if ("searchable".equals(option.getType())) {
                searchableColumns.addAll(parseColumns(option.getValue()));
            } else if ("orderable".equals(option.getType())) {
                orderableColumns.addAll(parseColumns(option.getValue()));
            } else if ("order".equals(option.getType())) {
                defaultOrderColumn = option.getAtrributeValue(L_COLUMN);
                defaultOrderDirection = option.getAtrributeValue("direction");
                if (predefinedFilters != null) {
                    for (PredefinedFilter predefinedFilter : predefinedFilters.values()) {
                        if (predefinedFilter.getOrderColumn() == null) {
                            predefinedFilter.setOrderColumn(defaultOrderColumn);
                            predefinedFilter.setOrderDirection(defaultOrderDirection);
                        }
                    }
                }
            } else if (L_COLUMN.equals(option.getType())) {
                parseColumnOption(option);
            } else if ("weakRelation".equals(option.getType())) {
                weakRelation = Boolean.parseBoolean(option.getValue());
            } else if ("fixedHeight".equals(option.getType())) {
                fixedHeight = Boolean.parseBoolean(option.getValue());
            }
        }
        if (defaultOrderColumn == null) {
            throwIllegalStateException("grid must contain 'order' option");
        }
    }

    private void parseColumnOption(final ComponentOption option) {
        GridComponentColumn column = new GridComponentColumn(option.getAtrributeValue("name"));
        String fields = option.getAtrributeValue("fields");
        if (fields != null) {
            for (FieldDefinition field : parseFields(fields, column)) {
                column.addField(field);
            }
        }
        column.setExpression(option.getAtrributeValue("expression"));
        String columnWidth = option.getAtrributeValue(L_WIDTH);
        if (columnWidth != null) {
            column.setWidth(Integer.valueOf(columnWidth));
        }
        if (option.getAtrributeValue("link") != null) {
            column.setLink(Boolean.parseBoolean(option.getAtrributeValue("link")));
        }
        if (option.getAtrributeValue("hidden") != null) {
            column.setHidden(Boolean.parseBoolean(option.getAtrributeValue("hidden")));
        }
        columns.put(column.getName(), column);
    }

    private Set<String> parseColumns(final String columns) {
        Set<String> set = new HashSet<String>();
        for (String column : columns.split("\\s*,\\s*")) {
            set.add(column);
        }
        return set;
    }

    private Set<FieldDefinition> parseFields(final String fields, final GridComponentColumn column) {
        Set<FieldDefinition> set = new HashSet<FieldDefinition>();
        for (String field : fields.split("\\s*,\\s*")) {
            FieldDefinition fieldDefiniton = getDataDefinition().getField(field);
            if (fieldDefiniton == null) {
                throwIllegalStateException(
                        "field = " + field + " in option column = " + column.getName() + " doesn't exists");
            }
            set.add(fieldDefiniton);
        }
        return set;
    }

    private void throwIllegalStateException(final String message) {
        throw new IllegalStateException(getViewDefinition().getPluginIdentifier() + "."
                + getViewDefinition().getName() + "#" + getPath() + ": " + message);
    }

    public Set<String> getOrderableColumns() {
        return orderableColumns;
    }

    public boolean isWeakRelation() {
        return weakRelation;
    }

    public String getDefaultOrderColumn() {
        return defaultOrderColumn;
    }

    public String getDefaultOrderDirection() {
        return defaultOrderDirection;
    }

    public Map<String, GridComponentColumn> getColumns() {
        // FIXME MAKU -> KRNA: I think we should return an unmodifable (immutable) map or (if mutability were intended) pack them
        // into SynchronizedMap.
        return Maps.filterEntries(columns,
                Predicates.compose(COLUMNS_VISIBLE_FOR_TENANT_PREDICATE, VALUE_FROM_MAP_ENTRY_FUNCTION));
    }

    public boolean isActivable() {
        return activable;
    }

    public FieldDefinition getBelongsToFieldDefinition() {
        return belongsToFieldDefinition;
    }

    public void setRowStyleResolver(final RowStyleResolver rowStyleResolver) {
        this.rowStyleResolver = rowStyleResolver;
    }

    public RowStyleResolver getRowStyleResolver() {
        return rowStyleResolver;
    }

    public void setCriteriaModifier(final CriteriaModifier criteriaModifier) {
        this.criteriaModifier = criteriaModifier;
    }

    public CriteriaModifier getCriteriaModifier() {
        return criteriaModifier;
    }

    public void setFixedHeight(final Boolean fixedHeight) {
        this.fixedHeight = fixedHeight;
    }

    public PredefinedFilter getDefaultPredefinedFilter() {
        return predefinedFilters.get(defaultPredefinedFilterName);
    }
}