Package org.libreplan.web.resources.search

Source Code of org.libreplan.web.resources.search.NewAllocationSelectorController$CriterionTreeNode

/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
*                         Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program 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, see <http://www.gnu.org/licenses/>.
*/

package org.libreplan.web.resources.search;

import static org.libreplan.web.I18nHelper._;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.resources.daos.IResourceLoadRatiosCalculator.ILoadRatiosDataType;
import org.libreplan.business.resources.daos.IResourcesSearcher.IResourcesQuery;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionType;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.ResourceType;
import org.libreplan.web.common.Util;
import org.libreplan.web.common.components.NewAllocationSelector.AllocationType;
import org.libreplan.web.common.components.ResourceAllocationBehaviour;
import org.libreplan.web.planner.allocation.INewAllocationsAdder;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zul.Constraint;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Div;
import org.zkoss.zul.Image;
import org.zkoss.zul.Label;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.Radio;
import org.zkoss.zul.Radiogroup;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.SimpleTreeModel;
import org.zkoss.zul.SimpleTreeNode;
import org.zkoss.zul.Tree;
import org.zkoss.zul.TreeModel;
import org.zkoss.zul.Treecell;
import org.zkoss.zul.Treeitem;
import org.zkoss.zul.TreeitemRenderer;
import org.zkoss.zul.Treerow;
import org.zkoss.zul.api.Listheader;

/**
* Controller for searching for {@link Resource}.
*
* @author Diego Pino Garcia <dpino@igalia.com>
* @author Javier Moran Rua <jmoran@igalia.com>
*
*/
public class NewAllocationSelectorController extends
        AllocationSelectorController {

    private static final BigDecimal AVALIABILITY_GOOD_VALUE = new BigDecimal(
            0.50);
    private static final BigDecimal AVALIABILITY_INTERMEDIUM_VALUE = new BigDecimal(
            0.25);

    private ResourceListRenderer resourceListRenderer = new ResourceListRenderer();

    private Radiogroup allocationTypeSelector;

    private Tree criterionsTree;

    private Listbox listBoxResources;

    private Label allocationSelectedItems;

    private Datebox startDateLoadRatiosDatebox, endDateLoadRatiosDatebox;

    private CriterionRenderer criterionRenderer = new CriterionRenderer();

    private AllocationType currentAllocationType;

    private ResourceAllocationBehaviour behaviour;

    public NewAllocationSelectorController(ResourceAllocationBehaviour behaviour) {
        this.behaviour = behaviour;
    }

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        super.doAfterCompose(comp);
        comp.setVariable("selectorController", this, true);
        initializeComponents();
    }

    private void initializeComponents() {
        initializeCriteriaTree();
        initializeListboxResources();
        initializeAllocationTypeSelector();
        initializeFilteringDatesConstraints();
    }

    private void initializeFilteringDatesConstraints() {
        startDateLoadRatiosDatebox
                .setConstraint(checkConstraintFilteringDate());
        endDateLoadRatiosDatebox.setConstraint(checkConstraintFilteringDate());
    }

    private void initializeCriteriaTree() {
        // Initialize criteria tree
        if (criterionsTree != null) {
            criterionsTree.addEventListener("onSelect", new EventListener() {

                // Whenever an element of the tree is selected, a search query
                // is executed, refreshing the results into the workers listbox
                @Override
                public void onEvent(Event event) {
                    searchResources("", getSelectedCriterions());
                    showSelectedAllocations();
                }
            });
        }
        criterionsTree.setTreeitemRenderer(criterionRenderer);
    }

    private void initializeListboxResources() {
        // Initialize found resources box
        listBoxResources.addEventListener(Events.ON_SELECT,
                new EventListener() {

                    @Override
                    public void onEvent(Event event) {
                        if (isGenericType()) {
                            returnToSpecificDueToResourceSelection();
                        }
                        showSelectedAllocations();
                    }
                });
        listBoxResources.setMultiple(behaviour.allowMultipleSelection());
        listBoxResources.setItemRenderer(getListitemRenderer());
    }

    private void initializeAllocationTypeSelector() {
        // Add onCheck listener
        listBoxResources.setItemRenderer(getListitemRenderer());

        // Initialize radio group of selector types
        allocationTypeSelector.addEventListener(Events.ON_CHECK,
                new EventListener() {

                    @Override
                    public void onEvent(Event event) {
                        Radio radio = (Radio) event.getTarget();
                        if (radio == null) {
                            return;
                        }
                        onType(AllocationType.valueOf(radio.getValue()));
                        showSelectedAllocations();
                    }
                });
        // Feed with values
        for (AllocationType each : behaviour.allocationTypes()) {
            allocationTypeSelector.appendChild(radio(each));
        }
        doInitialSelection();
    }

    private Radio radio(AllocationType allocationType) {
        Radio result = new Radio(allocationType.getName());
        result.setValue(allocationType.toString());
        return result;
    }

    private void onType(AllocationType type) {
        currentAllocationType = type;
        Util.reloadBindings(criterionsTree);
        refreshListBoxResources();
    }

    private void doInitialSelection() {
        Radio item = allocationTypeSelector.getItemAtIndex(0);
        currentAllocationType = AllocationType.valueOf(item.getValue());
        showSelectedAllocations();
        item.setSelected(true);
    }

    private void showSelectedAllocations() {
        allocationSelectedItems.setValue(buildSelectedAllocationsString());
    }

    private String buildSelectedAllocationsString() {
        if (currentAllocationType == AllocationType.SPECIFIC) {
            List<Resource> resources = getSelectedResources();
            return Resource.getCaptionFor(resources);
        } else {
            List<Criterion> criteria = getSelectedCriterions();
            return currentAllocationType.asCaption(criteria);
        }
    }

    private List<ResourceWithItsLoadRatios> getAllResources() {
        return addLoadRatiosCalculations(query().byResourceType(
                getType()).execute());
    }

    private List<ResourceWithItsLoadRatios> addLoadRatiosCalculations(
            List<? extends Resource> listResources) {

        List<ResourceWithItsLoadRatios> result = new ArrayList<ResourceWithItsLoadRatios>();

        for (Resource each : listResources) {
            ILoadRatiosDataType t = resourceLoadRatiosCalculator
                    .calculateLoadRatios(each, LocalDate
                            .fromDateFields(startDateLoadRatiosDatebox
                                    .getValue()),
                            LocalDate.fromDateFields(endDateLoadRatiosDatebox
                                    .getValue()), scenarioManager.getCurrent());
            result.add(new ResourceWithItsLoadRatios(each, t));
        }

        return result;
    }

    private ResourceType getType() {
        return behaviour.getType();
    }

    private IResourcesQuery<?> query() {
        return currentAllocationType.doQueryOn(resourcesSearcher);
    }

    private void refreshListBoxResources() {
        refreshListBoxResources(getAllResources());
    }

    private void refreshListBoxResources(
            List<ResourceWithItsLoadRatios> resources) {
        listBoxResources.setModel(new SimpleListModel(resources));
        triggerSortListBoxResources();
    }

    private void triggerSortListBoxResources() {
        for (Object child : listBoxResources.getListhead().getChildren()) {
            final Listheader hd = (Listheader) child;
            if (!"natural".equals(hd.getSortDirection())) {
                hd.sort("ascending".equals(hd.getSortDirection()), true);
            }
        }
    }

    private void returnToSpecificDueToResourceSelection() {
        currentAllocationType = AllocationType.SPECIFIC;
        List<Criterion> criteria = getSelectedCriterions();
        List<Resource> selectedWorkers = getSelectedWorkers();
        refreshListBoxResources(addLoadRatiosCalculations(query()
                .byCriteria(criteria).byResourceType(getType()).execute()));

        listBoxResources.renderAll(); // force render so list items has the
                                      // value property so the resources can be
                                      // selected

        selectWorkers(selectedWorkers);
        currentAllocationType.doTheSelectionOn(allocationTypeSelector);
    }

    private void selectWorkers(Collection<? extends Resource> selectedWorkers) {
        for (Resource each : selectedWorkers) {
            Listitem listItem = findListItemFor(each);
            if (listItem != null) {
                listItem.setSelected(true);
            }
        }
    }

    private Listitem findListItemFor(Resource resource) {
        @SuppressWarnings("unchecked")
        Collection<Listitem> items = listBoxResources.getItems();
        for (Listitem item : items) {
            Resource itemResource = ((ResourceWithItsLoadRatios) item
                    .getValue()).getResource();
            if (itemResource != null
                    && itemResource.getId().equals(resource.getId())) {
                return item;
            }
        }
        return null;
    }

    private static final EnumSet<AllocationType> genericTypes = EnumSet.of(
            AllocationType.GENERIC_MACHINES, AllocationType.GENERIC_WORKERS);

    private boolean isGenericType() {
        return genericTypes.contains(currentAllocationType);
    }

    @SuppressWarnings("unchecked")
    private void clearSelection(Listbox listBox) {
        Set<Listitem> selectedItems = new HashSet<Listitem>(
                listBox.getSelectedItems());
        for (Listitem each : selectedItems) {
            listBox.removeItemFromSelection(each);
        }
    }

    @SuppressWarnings("unchecked")
    private void clearSelection(Tree tree) {
        Set<Treeitem> selectedItems = new HashSet<Treeitem>(
                tree.getSelectedItems());
        for (Treeitem each : selectedItems) {
            tree.removeItemFromSelection(each);
        }
    }

    /**
     * Get input text, and search for workers
     *
     * @param event
     */
    public void searchWorkers(InputEvent event) {
        searchResources(event.getValue(), getSelectedCriterions());
    }

    /**
     * Does the actual search for workers, and refresh results
     *
     * @param name
     * @param criterions
     */
    private void searchResources(String name, List<Criterion> criterions) {
        final List<? extends Resource> resources = query().byName(name)
                .byCriteria(criterions).byResourceType(getType()).execute();
        refreshListBoxResources(addLoadRatiosCalculations(resources));
    }

    /**
     * Returns list of selected {@link Criterion}, selects only those which are
     * leaf nodes
     *
     * @return
     */
    @Override
    public List<Criterion> getSelectedCriterions() {
        List<Criterion> result = new ArrayList<Criterion>();

        Set<Treeitem> selectedItems = criterionsTree.getSelectedItems();
        for (Treeitem item : selectedItems) {
            CriterionTreeNode node = (CriterionTreeNode) item.getValue();

            if (node.getData() instanceof Criterion) {
                result.add((Criterion) node.getData());
            }
        }
        return result;
    }

    public ResourceListRenderer getListitemRenderer() {
        return resourceListRenderer;
    }

    @Override
    public void onClose() {
        clearAll();
    }

    @Override
    public void clearAll() {
    }

    public void open(final LocalDate start, final LocalDate end) {
        setStartFilteringDate(start);
        setEndFilteringDate(end);

        clearSelection(listBoxResources);
        clearSelection(criterionsTree);


        adHocTransactionService
                .runOnReadOnlyTransaction(new IOnTransaction<Void>() {
                    @Override
                    public Void execute() {
                        refreshListBoxResources();
                        criterionsTree.setModel(getCriterions());

                        return null;
                    }
                });

        doInitialSelection();
    }

    public List<Resource> getSelectedWorkers() {
        if (isGenericType()) {
            return allResourcesShown();
        } else {
            return getSelectedResources();
        }
    }

    @SuppressWarnings("unchecked")
    private List<Resource> allResourcesShown() {
        List<Resource> result = new ArrayList<Resource>();
        List<Listitem> selectedItems = listBoxResources.getItems();
        for (Listitem item : selectedItems) {
            result.add(((ResourceWithItsLoadRatios) item.getValue())
                    .getResource());
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private List<Resource> getSelectedResources() {
        List<Resource> result = new ArrayList<Resource>();
        Set<Listitem> selectedItems = listBoxResources.getSelectedItems();
        for (Listitem item : selectedItems) {

            result.add(((ResourceWithItsLoadRatios) item.getValue())
                    .getResource());
        }
        return result;
    }

    /**
     * @author Diego Pino García <dpino@igalia.com>
     *
     *         Encapsulates {@link SimpleTreeNode}
     *
     */
    private static class CriterionTreeNode extends SimpleTreeNode {

        public CriterionTreeNode(Object data, List<CriterionTreeNode> children) {
            super(data, children);
        }

        /**
         * Returns {@link CriterionTreeNode} name depending if node contains a
         * {@link Criterion} or a {@link CriterionType}
         */
        @Override
        public String toString() {
            if (getData() instanceof CriterionType) {
                return ((CriterionType) getData()).getName();
            }
            if (getData() instanceof Criterion) {
                return ((Criterion) getData()).getName();
            }

            return "";
        }
    }

    /**
     * Gets all {@link Criterion} and returns a {@link TreeModel} out of it
     *
     * This {@link TreeModel} is used to feed criterionsTree widget
     *
     * @return
     */
    public TreeModel getCriterions() {
        Map<CriterionType, Set<Criterion>> criterions = query().getCriteria();

        List<CriterionTreeNode> rootList = new ArrayList<CriterionTreeNode>();
        for (Entry<CriterionType, Set<Criterion>> entry : criterions.entrySet()) {
            rootList.add(asNode(entry.getKey(), entry.getValue()));
        }
        CriterionTreeNode root = new CriterionTreeNode("Root", rootList);
        return new SimpleTreeModel(root);
    }

    public List<AllocationType> getAllocationTypes() {
        return Arrays.asList(AllocationType.values());
    }

    /**
     * Converts {@link CriterionType} to {@link CriterionTreeNode}
     *
     * @param criterionType
     * @param criterions
     * @return
     */
    private CriterionTreeNode asNode(CriterionType criterionType,
            Set<Criterion> criterions) {
        return new CriterionTreeNode(criterionType,
                toNodeList(withoutParents(criterions)));
    }

    private List<Criterion> withoutParents(
            Collection<? extends Criterion> criterions) {
        List<Criterion> result = new ArrayList<Criterion>();
        for (Criterion each : criterions) {
            if (each.getParent() == null) {
                result.add(each);
            }
        }
        return result;
    }

    private List<CriterionTreeNode> toNodeList(
            Collection<? extends Criterion> criterions) {
        ArrayList<CriterionTreeNode> result = new ArrayList<CriterionTreeNode>();
        for (Criterion criterion : sortedByName(criterions)) {
            result.add(asNode(criterion));
        }
        return result;
    }

    private List<Criterion> sortedByName(
            Collection<? extends Criterion> criterions) {
        List<Criterion> result = new ArrayList<Criterion>(criterions);
        Collections.sort(result, Criterion.byName);
        return result;
    }

    /**
     * Converts {@link Criterion} to {@link CriterionTreeNode}
     *
     * @param criterion
     * @return
     */
    private CriterionTreeNode asNode(Criterion criterion) {
        return new CriterionTreeNode(criterion,
                toNodeList(criterion.getChildren()));
    }

    private static class ResourceWithItsLoadRatios implements
            Comparable<ResourceWithItsLoadRatios> {

        private Resource resource;
        private ILoadRatiosDataType ratios;

        public ResourceWithItsLoadRatios(Resource resource,
                ILoadRatiosDataType ratios) {
            Validate.notNull(resource);
            Validate.notNull(ratios);
            this.resource = resource;
            this.ratios = ratios;
        }

        public Resource getResource() {
            return this.resource;
        }

        public ILoadRatiosDataType getRatios() {
            return this.ratios;
        }

        @Override
        public int compareTo(ResourceWithItsLoadRatios o) {
            return this.resource.compareTo(o.getResource());
        }
    }

    /**
     * @author Diego Pino García <dpino@igalia.com>
     *
     *         Render for listBoxResources
     */
    private static class ResourceListRenderer implements ListitemRenderer {

        @Override
        public void render(Listitem item, Object data) {
            item.setValue(data);

            appendLabelResource(item);
        }

        private void appendLabelResource(Listitem item) {
            ResourceWithItsLoadRatios dataToRender = (ResourceWithItsLoadRatios) item
                    .getValue();

            Listcell cellName = new Listcell();
            Resource resource = dataToRender.getResource();
            cellName.appendChild(new Label(resource.getShortDescription()));
            item.appendChild(cellName);

            Listcell cellAvailability = new Listcell();
            BigDecimal availability = dataToRender.getRatios()
                    .getAvailiabilityRatio();
            Div totalDiv = new Div();
            totalDiv.setStyle("width:50px;height:12px;border: solid 1px black");
            Div containedDiv = new Div();
            String styleValue = "width:" + availability.movePointRight(2)
                    + "%;height:12px;background-color:"
                    + calculateRgba(availability) + ";float:left;left:0";
            containedDiv.setStyle(styleValue);
            Label l = new Label(availability.movePointRight(2).toString() + "%");
            l.setStyle("width:50px;margin-left: 12px");
            containedDiv.appendChild(l);
            totalDiv.appendChild(containedDiv);
            cellAvailability.appendChild(totalDiv);
            item.appendChild(cellAvailability);

            Listcell cellOvertime = new Listcell();
            BigDecimal overtime = dataToRender.getRatios().getOvertimeRatio();
            Label overtimeLabel = new Label(overtime.toString());
            cellOvertime.appendChild(overtimeLabel);
            if (!overtime.equals(BigDecimal.ZERO.setScale(2))) {
                overtimeLabel.setStyle("position: relative; top: -12px");
                Image img = new Image(
                        "/dashboard/img/value-meaning-negative.png");
                img.setStyle("width: 25px; position: relative; top: -5px");
                cellOvertime.appendChild(img);
            }
            item.appendChild(cellOvertime);
        }

        private String calculateRgba(BigDecimal avaliability) {
            String result;
            if (avaliability.compareTo(AVALIABILITY_INTERMEDIUM_VALUE) < 0) {
                result = "rgba(150,0,0,0.3)";
            } else if (avaliability.compareTo(AVALIABILITY_GOOD_VALUE) < 0) {
                result = "rgba(255,255,0,0.5)";
            } else {
                result = "rgba(102,204,0,0.3)";
            }

            return result;
        }
    }

    public CriterionRenderer getCriterionRenderer() {
        return criterionRenderer;
    }

    /**
     * @author Diego Pino Garcia <dpino@igalia.com>
     *
     *         Render for criterionsTree
     *
     *         I had to implement a renderer for the Tree. Every item in the
     *         tree should be set as opened at first.
     *
     *         I tried to do this by iterating through the list of items after
     *         setting the model at doAfterCompose, but I got a
     *         ConcurrentModificationException. It seems that at that point some
     *         other component was using the list of items, so it was not
     *         possible to modify it. There's not other point where to
     *         initialize components but doAfterCompose.
     *
     *         Finally, I tried this solution and it works
     *
     */
    private static class CriterionRenderer implements TreeitemRenderer {

        /**
         * Copied verbatim from org.zkoss.zul.Tree;
         */
        @Override
        public void render(Treeitem ti, Object node) {
            Treecell tc = new Treecell(Objects.toString(node));
            Treerow tr = null;
            ti.setValue(node);
            if (ti.getTreerow() == null) {
                tr = new Treerow();
                tr.setParent(ti);
                ti.setOpen(true); // Expand node
            } else {
                tr = ti.getTreerow();
                tr.getChildren().clear();
            }
            tc.setParent(tr);
        }

    }

    @Override
    public void addTo(INewAllocationsAdder allocationsAdder) {
        currentAllocationType.addTo(this, allocationsAdder);
    }

    public void allowSelectMultipleResources(boolean multiple) {
        listBoxResources.setMultiple(multiple);
    }

    public boolean isAllowSelectMultipleResources() {
        return listBoxResources.isMultiple();
    }

    public void setEndFilteringDate(LocalDate d) {
        endDateLoadRatiosDatebox.setValue(asDate(d));
    }

    public void setStartFilteringDate(LocalDate date) {
        startDateLoadRatiosDatebox.setValue(asDate(date));
    }

    private static Date asDate(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.toDateTimeAtStartOfDay().toDate();
    }

    public void updateLoadRatios() {
        searchResources("", getSelectedCriterions());
    }

    public Constraint  checkConstraintFilteringDate() {
        return new Constraint() {
            @Override
            public void validate(Component comp, Object value) throws WrongValueException {
                if (value == null) {
                    if (comp.getId().equals("startDateLoadRatiosDatebox")) {
                        throw new WrongValueException(comp,
                                _("Start filtering date cannot be empty"));
                    } else if (comp.getId().equals("endDateLoadRatiosDatebox")) {
                        throw new WrongValueException(comp,
                                _("End filtering date cannot be empty"));
                    }
                }

                Date startDate = null;
                if (comp.getId().equals("startDateLoadRatiosDatebox")) {
                    startDate = (Date) value;
                } else {
                    startDate = (Date) startDateLoadRatiosDatebox.getRawValue();
                }

                Date endDate = null;
                if (comp.getId().equals("endDateLoadRatiosDatebox")) {
                    endDate = (Date) value;
                } else {
                    endDate = (Date) endDateLoadRatiosDatebox.getRawValue();
                }

                if ((startDate != null) && (endDate != null)) {
                    if ((startDate.after(endDate))
                            || (startDate.equals(endDate))) {
                        throw new WrongValueException(comp,_("Start filtering date must be before than end filtering date"));
                    }
                }
            }
        };
    }

}
TOP

Related Classes of org.libreplan.web.resources.search.NewAllocationSelectorController$CriterionTreeNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.