com.flexive.war.beans.admin.search.ResultPreferencesBean.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.war.beans.admin.search.ResultPreferencesBean.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) backend application.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) backend application is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation;
 *  either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/licenses/gpl.html.
 *  A copy is found in the textfile GPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library 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 General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.war.beans.admin.search;

import com.flexive.faces.FxJsfUtils;
import com.flexive.faces.beans.MessageBean;
import com.flexive.faces.beans.SearchResultBean;
import com.flexive.faces.beans.SelectBean;
import com.flexive.faces.listener.JsfPhaseListener;
import com.flexive.faces.messages.FxFacesMsgErr;
import com.flexive.faces.messages.FxFacesMsgInfo;
import com.flexive.faces.messages.FxFacesMsgWarn;
import com.flexive.shared.CacheAdmin;
import com.flexive.shared.FxSharedUtils;
import com.flexive.shared.exceptions.FxApplicationException;
import com.flexive.shared.exceptions.FxNotFoundException;
import com.flexive.shared.exceptions.FxRuntimeException;
import com.flexive.shared.search.*;
import com.flexive.shared.structure.FxEnvironment;
import com.flexive.shared.structure.FxGroupAssignment;
import com.flexive.shared.structure.FxPropertyAssignment;
import com.flexive.shared.structure.FxType;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.component.html.ext.HtmlDataTable;

import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;
import javax.faces.model.DataModel;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import java.io.Serializable;
import java.util.*;

import static com.flexive.shared.EJBLookup.getResultPreferencesEngine;

/**
 * Bean for creating and updating result preferences.
 *
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @version $Rev$
 */
public class ResultPreferencesBean implements Serializable {
    private static final long serialVersionUID = 7084947975179682271L;
    private static final Log LOG = LogFactory.getLog(ResultPreferencesBean.class);

    /**
     * Tomahawk HtmlDataTable wrapper that exposes the internal data model.
     * We need this for other components depending on data tables that persist their
     * model in their viewState (aka preserveDataModel=true).
     */
    public static class WrappedHtmlDataTable extends HtmlDataTable {
        @SuppressWarnings({ "MethodOverridesPrivateMethodOfSuperclass" })
        @Override
        public DataModel getDataModel() {
            return super.getDataModel();
        }
    }

    private long type = -1;
    private ResultViewType viewType = ResultViewType.LIST;
    private ResultLocation location = AdminResultLocations.ADMIN;
    private ResultPreferencesEdit resultPreferences = null;
    private String addPropertyName = null;
    private String addOrderByName = null;
    private SortDirection addOrderByDirection = SortDirection.ASCENDING;
    private int editColumnIndex = -1;
    private boolean forceSystemDefault;

    // cached select list
    private List<SelectItem> properties = null;
    private long cachedTypeId = -1;
    private Map<String, String> propertyLabelMap = null;
    private List<SelectItem> types;

    // access current selected columns directly from datatable component 
    private transient WrappedHtmlDataTable selectedColumnsTable;

    public ResultPreferencesBean() {
        parseRequestParameters();
    }

    /**
     * Parse the request parameters and perform actions as requested.
     * Works only if the ResultPreferencesBean remains request-scoped!
     */
    private void parseRequestParameters() {
        try {
            String action = FxJsfUtils.getParameter("action");
            if (StringUtils.isBlank(action)) {
                // no action requested
                return;
            }
            if ("loadSystemDefault".equals(action)) {
                forceSystemDefault = true;
            }
        } catch (Exception e) {
            LOG.error("Failed to parse request parameters: " + e.getMessage(), e);
        }
    }

    public String show() {
        return "resultPreferences";
    }

    public String save() {
        try {
            getResultPreferencesEngine().save(getResultPreferences(), type, viewType, location);
            new FxFacesMsgInfo("ResultPreferences.nfo.saved").addToContext();

            // invalidate cached result from search bean
            FxJsfUtils.getManagedBean(SearchResultBean.class).setResult(null);
        } catch (FxApplicationException e) {
            new FxFacesMsgErr("ResultPreferences.err.save", e).addToContext();
        }
        return show();
    }

    public String remove() {
        try {
            getResultPreferencesEngine().remove(type, viewType, location);
            resultPreferences = null;
            new FxFacesMsgInfo("ResultPreferences.nfo.removed").addToContext();
        } catch (FxApplicationException e) {
            new FxFacesMsgErr("ResultPreferences.err.remove", e).addToContext();
        }
        return show();
    }

    public void saveSystemDefault() {
        try {
            getResultPreferencesEngine().saveSystemDefault(getResultPreferences(), type, viewType, location);
            new FxFacesMsgInfo("ResultPreferences.nfo.savedSystemDefault").addToContext();
        } catch (FxApplicationException e) {
            new FxFacesMsgErr("ResultPreferences.err.save", e).addToContext();
        }
    }

    public void loadSystemDefault() {
        forceSystemDefault = true;
        resultPreferences = null;
    }

    public String cancel() {
        return ((SearchResultBean) FxJsfUtils.getManagedBean("fxSearchResultBean")).show();
    }

    public void addColumnProperty() {
        // split into property name and (optional) suffix
        final String[] name = StringUtils.split(addPropertyName, ".", 2);
        getResultPreferences()
                .addSelectedColumn(new ResultColumnInfo(Table.CONTENT, name[0], name.length > 1 ? name[1] : null));
        addPropertyName = null;
        properties = null; // refresh available properties
    }

    public void removeColumnProperty() {
        if (editColumnIndex == -1) {
            return;
        }
        try {
            final ResultColumnInfo info = getResultPreferences().removeSelectedColumn(editColumnIndex);
            // remove property from order by clause too
            List<ResultOrderByInfo> removeOrderByColumns = new ArrayList<ResultOrderByInfo>();
            for (ResultOrderByInfo orderByInfo : getResultPreferences().getOrderByColumns()) {
                if (orderByInfo.getColumnName().equals(info.getColumnName())) {
                    removeOrderByColumns.add(orderByInfo);
                }
            }
            for (ResultOrderByInfo removeInfo : removeOrderByColumns) {
                getResultPreferences().removeOrderByColumn(removeInfo);
            }
            properties = null; // refresh available properties
        } catch (FxRuntimeException e) {
            new FxFacesMsgErr("ResultPreferences.err.removeRow", e).addToContext();
        }
    }

    public void moveColumnPropertyUp() {
        moveColumnProperty(-1);
    }

    public void moveColumnPropertyDown() {
        moveColumnProperty(1);
    }

    public void moveColumnPropertyTop() {
        moveColumnProperty(-editColumnIndex);
    }

    public void moveColumnPropertyBottom() {
        moveColumnProperty(getSelectedColumns().size() - editColumnIndex);
    }

    private void moveColumnProperty(int moveDelta) {
        if (editColumnIndex == -1) {
            return;
        }
        try {
            ResultColumnInfo info = getResultPreferences().removeSelectedColumn(editColumnIndex);
            getResultPreferences().addSelectedColumn(editColumnIndex + moveDelta, info);
        } catch (FxRuntimeException e) {
            new FxFacesMsgErr("ResultPreferences.err.moveRow", e).addToContext();
        }
    }

    public void moveOrderByPropertyUp() {
        moveOrderByProperty(-1);
    }

    public void moveOrderByPropertyDown() {
        moveOrderByProperty(1);
    }

    private void moveOrderByProperty(int moveDelta) {
        if (editColumnIndex == -1) {
            return;
        }
        try {
            ResultOrderByInfo info = getResultPreferences().removeOrderByColumn(editColumnIndex);
            getResultPreferences().addOrderByColumn(editColumnIndex + moveDelta, info);
        } catch (FxRuntimeException e) {
            new FxFacesMsgErr("ResultPreferences.err.moveRow", e).addToContext();
        }
    }

    public void addOrderByProperty() {
        getResultPreferences()
                .addOrderByColumn(new ResultOrderByInfo(Table.CONTENT, addOrderByName, null, addOrderByDirection));
    }

    public void removeOrderByProperty() {
        if (editColumnIndex == -1) {
            return;
        }
        try {
            getResultPreferences().removeOrderByColumn(editColumnIndex);
        } catch (FxRuntimeException e) {
            new FxFacesMsgErr("ResultPreferences.err.removeRow", e).addToContext();
        }
    }

    public void reloadPreferences() {
        this.resultPreferences = null;
    }

    public long getType() {
        return type;
    }

    public void setType(long type) {
        this.type = type;
    }

    public List<SelectItem> getTypes() throws FxApplicationException {
        if (types == null) {
            types = new ArrayList<SelectItem>(FxJsfUtils.getManagedBean(SelectBean.class).getTypes());
            // remove root type
            for (Iterator<SelectItem> iterator = types.iterator(); iterator.hasNext();) {
                if (iterator.next().getValue().equals(FxType.ROOT_ID)) {
                    iterator.remove();
                    break;
                }
            }
            types.add(0, new SelectItem(-1L, FxJsfUtils.getLocalizedMessage("ResultPreferences.label.allTypes")));
        }
        return types;
    }

    public ResultViewType getViewType() {
        return viewType;
    }

    public void setViewType(ResultViewType viewType) {
        this.viewType = viewType;
    }

    public boolean isThumbnails() {
        return ResultViewType.THUMBNAILS.equals(viewType);
    }

    public ResultLocation getLocation() {
        return location;
    }

    public void setLocation(ResultLocation location) {
        this.location = location;
    }

    public String getAddPropertyName() {
        return addPropertyName;
    }

    public void setAddPropertyName(String addPropertyName) {
        this.addPropertyName = addPropertyName;
    }

    public int getEditColumnIndex() {
        return editColumnIndex;
    }

    public void setEditColumnIndex(int editColumnIndex) {
        this.editColumnIndex = editColumnIndex;
    }

    public SortDirection getAddOrderByDirection() {
        return addOrderByDirection;
    }

    public void setAddOrderByDirection(SortDirection addOrderByDirection) {
        this.addOrderByDirection = addOrderByDirection;
    }

    public String getAddOrderByName() {
        return addOrderByName;
    }

    public void setAddOrderByName(String addOrderByName) {
        this.addOrderByName = addOrderByName;
    }

    public boolean isCustomized() throws FxApplicationException {
        return getResultPreferencesEngine().isCustomized(type, viewType, location);
    }

    public boolean isForceSystemDefault() {
        return forceSystemDefault;
    }

    public void setForceSystemDefault(boolean forceSystemDefault) {
        this.forceSystemDefault = forceSystemDefault;
    }

    public ResultPreferencesEdit getResultPreferences() {
        if (resultPreferences == null) {
            // fetch result preferences from DB (the current value of ResultPreferences#selectedColumn is
            // persisted by the t:dataTable, so we do not have to remember them inside the bean)
            try {
                if (forceSystemDefault) {
                    resultPreferences = getResultPreferencesEngine().loadSystemDefault(type, viewType, location)
                            .getEditObject();
                } else {
                    resultPreferences = getResultPreferencesEngine().load(type, viewType, location).getEditObject();
                }
            } catch (FxNotFoundException e) {
                new FxFacesMsgWarn("ResultPreferences.wng.notFound").addToContext();
                resultPreferences = new ResultPreferences().getEditObject();
            } catch (FxApplicationException e) {
                new FxFacesMsgErr("ResultPreferences.err.load", e).addToContext();
            }
        }
        return resultPreferences;
    }

    public List<SelectItem> getProperties() {
        if (properties == null || cachedTypeId != getType()) {
            final FxEnvironment environment = CacheAdmin.getFilteredEnvironment();
            final FxType type = environment.getType(getType() != -1 ? getType() : FxType.ROOT_ID);
            final List<FxPropertyAssignment> contentProperties = Lists.newArrayList(type.getAssignedProperties());
            for (FxGroupAssignment groupAssignment : type.getAssignedGroups()) {
                addGroupAssignments(groupAssignment, contentProperties);
            }

            // add virtual properties...
            final MessageBean messageBean = MessageBean.getInstance();
            final SelectItemGroup virtualGroup = new SelectItemGroup(
                    messageBean.getMessage("ResultPreferences.label.group.virtual"));
            virtualGroup.setSelectItems(new SelectItem[] {
                    new SelectItem("@pk", messageBean.getMessage("ResultPreferences.label.property.pk"), null,
                            columnSelected("@pk")),
                    new SelectItem("@path", messageBean.getMessage("ResultPreferences.label.property.path"), null,
                            columnSelected("@path")),
                    new SelectItem("@permissions",
                            messageBean.getMessage("ResultPreferences.label.property.permissions"), null,
                            columnSelected("@permissions")),
                    new SelectItem("@lock", messageBean.getMessage("ResultPreferences.label.property.lock"), null,
                            columnSelected("@lock")) });
            properties = new ArrayList<SelectItem>(contentProperties.size() + 10);
            properties.add(virtualGroup);
            // add type properties
            properties.addAll(filteredPropertiesGroup(contentProperties,
                    messageBean.getMessage("ResultPreferences.label.group.type", type.getDisplayName()), false));
            // add derived properties
            properties.addAll(filteredPropertiesGroup(contentProperties,
                    messageBean.getMessage("ResultPreferences.label.group.derived"), true));

            cachedTypeId = getType();
        }
        return properties;
    }

    private boolean columnSelected(String columnName) {
        for (ResultColumnInfo columnInfo : getSelectedColumns()) {
            if (columnInfo.getColumnName().equalsIgnoreCase(columnName)) {
                return true;
            }
        }
        return false;
    }

    private void addGroupAssignments(FxGroupAssignment group, List<FxPropertyAssignment> contentProperties) {
        contentProperties.addAll(group.getAssignedProperties());
        for (FxGroupAssignment child : group.getAssignedGroups()) {
            addGroupAssignments(child, contentProperties);
        }
    }

    private List<SelectItemGroup> filteredPropertiesGroup(List<FxPropertyAssignment> contentProperties,
            String title, boolean includeDerived) {

        // assign properties to the type that originally defined them
        final HashMultimap<FxType, FxPropertyAssignment> byType = HashMultimap.create();
        final FxType rootType = CacheAdmin.getEnvironment().getType(FxType.ROOT_ID);
        for (FxPropertyAssignment assignment : contentProperties) {
            if (includeDerived && assignment.isDerivedAssignment()
                    || (!includeDerived && !assignment.isDerivedAssignment())) {
                // find type that first defined used or defined an assignment with our property
                FxType type = assignment.getAssignedType();
                while (type.getParent() != null && !type.getParent()
                        .getAssignmentsForProperty(assignment.getProperty().getId()).isEmpty()) {
                    type = type.getParent();
                }
                // attach system-internal properties (ID, ACL, ...) to root type
                byType.put(assignment.getProperty().isSystemInternal() ? rootType : type, assignment);
            }
        }

        final List<SelectItemGroup> result;
        if (!includeDerived) {
            // just our type, return all properties in one group
            result = Arrays.asList(createSelectItemGroup(title, byType.values()));
        } else {
            // all derived types, group by type
            result = new ArrayList<SelectItemGroup>();

            // root types come last
            final List<FxType> types = Lists.newArrayList(byType.keys().elementSet());
            Collections.sort(types, new Comparator<FxType>() {
                @Override
                public int compare(FxType o1, FxType o2) {
                    return o1.getParent() == null ? 1 : o1.getParent().equals(o2) ? -1 : 0;
                }
            });
            for (FxType type : types) {
                result.add(createSelectItemGroup(title + ": " + type.getLabel(), byType.get(type)));
            }
        }

        return result;
    }

    private SelectItemGroup createSelectItemGroup(String title, Collection<FxPropertyAssignment> assignments) {
        final SelectItemGroup group = new SelectItemGroup(title);
        final List<SelectItem> items = new ArrayList<SelectItem>();
        for (FxPropertyAssignment assignment : assignments) {
            items.add(createSelectItem(assignment));
        }
        Collections.sort(items, new FxJsfUtils.SelectItemSorter());
        group.setSelectItems(items.toArray(new SelectItem[items.size()]));
        return group;
    }

    private SelectItem createSelectItem(FxPropertyAssignment assignment) {
        // add parent group path
        final StringBuilder label = new StringBuilder();
        FxGroupAssignment parent = assignment.getParentGroupAssignment();
        while (parent != null) {
            label.insert(0, parent.getLabel() + "/");
            parent = parent.getParentGroupAssignment();
        }
        // add property label
        label.append(assignment.getProperty().getLabel());

        final String value;
        final String propertyName = assignment.getProperty().getName();
        if ("CAPTION".equals(propertyName)) {
            // the caption property is often renamed (which breaks assignment inheritance)
            value = "CAPTION";
        } else if ("TYPEDEF".equals(propertyName)) {
            // select the type label instead
            value = "TYPEDEF.DESCRIPTION";
        } else if ("ACL".equals(propertyName)) {
            value = "ACL.LABEL";
        } else if ("STEP".equals(propertyName)) {
            value = "STEP.LABEL";
        } else if ("CREATED_BY".equals(propertyName) || "MODIFIED_BY".equals(propertyName)) {
            value = assignment.getProperty().getName() + ".USERNAME";
        } else {
            value = "#" + assignment.getXPath();
        }
        return new SelectItem(value, label.toString(), null,
                // disable properties/assignments that are already selected
                columnSelected(assignment.getProperty().getName()) || columnSelected("#" + assignment.getXPath()));
    }

    public List<SelectItem> getSelectedProperties() {
        // caching this is not trivial because the selectedColumns list
        // is updated between phases by the datatable
        final FxEnvironment environment = CacheAdmin.getFilteredEnvironment();
        final List<ResultColumnInfo> columns = getSelectedColumns();
        List<SelectItem> result = new ArrayList<SelectItem>(columns.size());
        for (ResultColumnInfo info : columns) {
            result.add(new SelectItem(info.getPropertyName(), info.getLabel(environment)));
        }
        return result;
    }

    @SuppressWarnings({ "unchecked" })
    private List<ResultColumnInfo> getSelectedColumns() {
        final List<ResultColumnInfo> columns;
        if (JsfPhaseListener.isInPhase(PhaseId.PROCESS_VALIDATIONS)) {
            columns = (List) ((WrappedHtmlDataTable) getSelectedColumnsTable()).getDataModel().getWrappedData();
        } else {
            columns = getResultPreferences().getSelectedColumns();
        }
        return columns;
    }

    public HtmlDataTable getSelectedColumnsTable() {
        if (selectedColumnsTable == null) {
            selectedColumnsTable = new WrappedHtmlDataTable();
        } else {
            UIComponent parent = selectedColumnsTable.getParent();
            while (parent.getParent() != null) {
                parent = parent.getParent();
            }
            if (parent instanceof UIViewRoot && parent != FacesContext.getCurrentInstance().getViewRoot()) {
                // create new table when view root changes
                System.out.println("View root changed - creating new wrapped table.");
                selectedColumnsTable = new WrappedHtmlDataTable();
            }
        }
        return selectedColumnsTable;
    }

    public void setSelectedColumnsTable(HtmlDataTable selectedColumnsTable) {
        if (selectedColumnsTable instanceof WrappedHtmlDataTable) {
            this.selectedColumnsTable = (WrappedHtmlDataTable) selectedColumnsTable;
        } else {
            throw new IllegalArgumentException("Invalid data table class: "
                    + (selectedColumnsTable != null ? selectedColumnsTable.getClass().getCanonicalName() : "null"));
        }
    }

    /**
     * Provides the property labels for the result preferences page, including virtual properties like @pk.
     *
     * @return the property labels for the result preferences page, including virtual properties like @pk.
     */
    public Map<String, String> getPropertyLabelMap() {
        if (propertyLabelMap == null) {
            propertyLabelMap = FxSharedUtils.getMappedFunction(new FxSharedUtils.ParameterMapper<String, String>() {
                private static final long serialVersionUID = -1140857482270400036L;
                private FxEnvironment environment = CacheAdmin.getFilteredEnvironment();

                @Override
                public String get(Object key) {
                    if (key == null) {
                        return null;
                    }
                    final String name = key.toString();
                    if (name.charAt(0) == '@') {
                        return MessageBean.getInstance()
                                .getMessage("ResultPreferences.label.property." + name.substring(1));
                    } else if (name.charAt(0) == '#') {
                        return environment.getAssignment(stripTableSelectors(name.substring(1)))
                                .getDisplayName(true);
                    } else {
                        return environment.getProperty(stripTableSelectors(name)).getLabel().getBestTranslation();
                    }
                }
            }, true);
        }
        return propertyLabelMap;
    }

    private String stripTableSelectors(String name) {
        final int pos = name.indexOf('.');
        return pos != -1 ? name.substring(0, pos) : name;
    }
}