/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo Framework * Version: 1.4 * * 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.tree; import static com.qcadoo.model.api.types.TreeType.NODE_NUMBER_FIELD; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.google.common.collect.Lists; import com.qcadoo.model.api.Entity; import com.qcadoo.model.api.EntityOpResult; import com.qcadoo.model.api.EntityTree; import com.qcadoo.model.api.EntityTreeNode; import com.qcadoo.model.api.FieldDefinition; import com.qcadoo.model.api.expression.ExpressionUtils; import com.qcadoo.model.api.search.SearchCriteriaBuilder; import com.qcadoo.model.api.search.SearchRestrictions; import com.qcadoo.model.api.types.TreeType; import com.qcadoo.model.api.utils.TreeNumberingServiceImpl; import com.qcadoo.model.internal.DetachedEntityTreeImpl; import com.qcadoo.view.api.components.TreeComponent; import com.qcadoo.view.internal.components.FieldComponentState; public final class TreeComponentState extends FieldComponentState implements TreeComponent { public static final String JSON_SELECTED_ENTITY_ID = "selectedEntityId"; public static final String JSON_BELONGS_TO_ENTITY_ID = "belongsToEntityId"; public static final String JSON_ROOT_NODE_ID = "root"; public static final String JSON_OPENED_NODES_ID = "openedNodes"; public static final String JSON_TREE_STRUCTURE = "treeStructure"; private static final String INITIAL_NODE_NUMBER_VALUE = "1"; private static final String CHILDREN = "children"; private final TreeEventPerformer eventPerformer = new TreeEventPerformer(); private TreeNode rootNode; private List<Long> openedNodes; private Long selectedEntityId; private JSONArray treeStructure; private final FieldDefinition belongsToFieldDefinition; private Long belongsToEntityId; private final Map<String, TreeDataType> dataTypes; private Map<Long, Entity> nodes = new HashMap<Long, Entity>(); public TreeComponentState(final FieldDefinition scopeField, final Map<String, TreeDataType> dataTypes, final TreeComponentPattern pattern) { super(pattern); belongsToFieldDefinition = scopeField; this.dataTypes = dataTypes; registerEvent("initialize", eventPerformer, "initialize"); registerEvent("initializeAfterBack", eventPerformer, "initializeAfterBack"); registerEvent("refresh", eventPerformer, "refresh"); registerEvent("select", eventPerformer, "selectEntity"); registerEvent("remove", eventPerformer, "removeSelectedEntity"); registerEvent("customAction", eventPerformer, "customAction"); registerEvent("save", eventPerformer, "save"); registerEvent("clear", eventPerformer, "clear"); } @Override protected void initializeContent(final JSONObject json) throws JSONException { super.initializeContent(json); if (json.has(JSON_SELECTED_ENTITY_ID) && !json.isNull(JSON_SELECTED_ENTITY_ID)) { selectedEntityId = json.getLong(JSON_SELECTED_ENTITY_ID); } if (json.has(JSON_BELONGS_TO_ENTITY_ID) && !json.isNull(JSON_BELONGS_TO_ENTITY_ID)) { belongsToEntityId = json.getLong(JSON_BELONGS_TO_ENTITY_ID); } if (json.has(JSON_OPENED_NODES_ID) && !json.isNull(JSON_OPENED_NODES_ID)) { JSONArray openNodesArray = json.getJSONArray(JSON_OPENED_NODES_ID); for (int i = 0; i < openNodesArray.length(); i++) { addOpenedNode(openNodesArray.getLong(i)); } } if (json.has(JSON_TREE_STRUCTURE) && !json.isNull(JSON_TREE_STRUCTURE)) { treeStructure = json.getJSONArray(JSON_TREE_STRUCTURE); } if (belongsToEntityId == null) { setEnabled(false); } } @Override protected JSONObject renderContent() throws JSONException { if (rootNode == null) { reload(); } JSONObject json = super.renderContent(); json.put(JSON_SELECTED_ENTITY_ID, selectedEntityId); json.put(JSON_BELONGS_TO_ENTITY_ID, belongsToEntityId); if (openedNodes != null) { JSONArray openedNodesArray = new JSONArray(); for (Long openedNodeId : openedNodes) { openedNodesArray.put(openedNodeId); } json.put(JSON_OPENED_NODES_ID, openedNodesArray); } if (rootNode != null) { json.put(JSON_ROOT_NODE_ID, rootNode.toJson()); } return json; } @Override public void onFieldEntityIdChange(final Long fieldEntityId) { if (belongsToEntityId != null && !belongsToEntityId.equals(fieldEntityId)) { setSelectedEntityId(null); } this.belongsToEntityId = fieldEntityId; setEnabled(fieldEntityId != null); } public List<Long> getOpenedNodes() { return openedNodes; } public void setOpenedNodes(final List<Long> openedNodes) { this.openedNodes = openedNodes; } public void addOpenedNode(final Long nodeId) { if (openedNodes == null) { openedNodes = new LinkedList<Long>(); } openedNodes.add(nodeId); } @Override public Object getFieldValue() { if (treeStructure == null) { return null; } if (treeStructure.length() == 0) { return new ArrayList<Entity>(); } if (treeStructure.length() > 1) { addMessage("qcadooView.validate.field.error.multipleRoots", MessageType.FAILURE); return null; } Entity entity = belongsToFieldDefinition.getDataDefinition().get(belongsToEntityId); EntityTree tree = entity.getTreeField(belongsToFieldDefinition.getName()); nodes = new HashMap<Long, Entity>(); for (Entity node : tree) { node.setField(CHILDREN, new ArrayList<Entity>()); node.setField("parent", null); nodes.put(node.getId(), node); } try { Entity parent = nodes.get(treeStructure.getJSONObject(0).getLong("id")); if (treeStructure.getJSONObject(0).has(CHILDREN)) { reorganize(parent, treeStructure.getJSONObject(0).getJSONArray(CHILDREN), Lists.newLinkedList(Lists.newArrayList(INITIAL_NODE_NUMBER_VALUE))); } return Collections.singletonList(parent); } catch (JSONException e) { throw new IllegalStateException(e.getMessage(), e); } } @SuppressWarnings("unchecked") private void reorganize(final Entity parent, final JSONArray childrens, final Deque<String> nodeNumberChain) throws JSONException { parent.setField(TreeType.NODE_NUMBER_FIELD, TreeNumberingServiceImpl.convertCollectionToString(nodeNumberChain)); int charNumber = 0; for (int i = 0; i < childrens.length(); i++) { Deque<String> newNodeNumberBranch = Lists.newLinkedList(nodeNumberChain); if (childrens.length() == 1) { TreeNumberingServiceImpl.incrementLastChainNumber(newNodeNumberBranch); } else { TreeNumberingServiceImpl.incrementLastChainCharacter(newNodeNumberBranch, charNumber++); } Entity nodeEntity = nodes.get(childrens.getJSONObject(i).getLong("id")); ((List<Entity>) parent.getField(CHILDREN)).add(nodeEntity); if (childrens.getJSONObject(i).has(CHILDREN)) { reorganize(nodeEntity, childrens.getJSONObject(i).getJSONArray(CHILDREN), newNodeNumberBranch); } } } @Override public void setFieldValue(final Object value) { if (!(value instanceof EntityTree) || !checkIfTreeContainsEntity((EntityTree) value, selectedEntityId)) { setSelectedEntityId(null); } requestRender(); requestUpdateState(); } private boolean checkIfTreeContainsEntity(final EntityTree tree, final Long entityId) { if (entityId == null) { return false; } if (tree instanceof DetachedEntityTreeImpl) { return ((DetachedEntityTreeImpl) tree).checkIfTreeContainsEntity(entityId); } SearchCriteriaBuilder searchBuilder = tree.find().add(SearchRestrictions.idEq(entityId)); return searchBuilder.list().getTotalNumberOfEntities() > 0; } /* * (non-Javadoc) * @see com.qcadoo.view.components.tree.ITreeComponentState#getSelectedEntityId() */ @Override public Long getSelectedEntityId() { return selectedEntityId; } public void setSelectedEntityId(final Long selectedEntityId) { this.selectedEntityId = selectedEntityId; notifyEntityIdChangeListeners(parseSelectedIdForListeners(selectedEntityId)); } public TreeNode getRootNode() { return rootNode; } public void setRootNode(final TreeNode rootNode) { this.rootNode = rootNode; } public void setRootNode(final EntityTreeNode rootNodeEntity) { if (rootNodeEntity == null) { return; } rootNode = createNode(rootNodeEntity, Lists.newLinkedList(Lists.newArrayList(INITIAL_NODE_NUMBER_VALUE))); if (openedNodes == null) { addOpenedNode(rootNode.getId()); } } private Long parseSelectedIdForListeners(final Long selectedEntityId) { if (selectedEntityId == null || selectedEntityId == 0) { return null; } return selectedEntityId; } private void reload() { if (belongsToEntityId == null) { return; } Entity entity = belongsToFieldDefinition.getDataDefinition().get(belongsToEntityId); EntityTree tree = entity.getTreeField(belongsToFieldDefinition.getName()); if (tree == null || tree.getRoot() == null) { return; } rootNode = createNode(tree.getRoot(), Lists.newLinkedList(Lists.newArrayList(INITIAL_NODE_NUMBER_VALUE))); if (openedNodes == null) { addOpenedNode(rootNode.getId()); } } private TreeNode createNode(final EntityTreeNode entityTreeNode, final Deque<String> nodeNumberChain) { entityTreeNode.setField(NODE_NUMBER_FIELD, TreeNumberingServiceImpl.convertCollectionToString(nodeNumberChain)); List<EntityTreeNode> childs = entityTreeNode.getChildren(); TreeDataType entityType = dataTypes.get(entityTreeNode.getEntityNoteType()); String nodeLabel = ExpressionUtils.getValue(entityTreeNode, entityType.getNodeLabelExpression(), getLocale()); TreeNode node = new TreeNode(entityTreeNode.getId(), nodeLabel, entityType); int charNumber = 0; for (EntityTreeNode childEntityTreeNode : childs) { Deque<String> newNodeNumberBranch = Lists.newLinkedList(nodeNumberChain); if (childs.size() == 1) { TreeNumberingServiceImpl.incrementLastChainNumber(newNodeNumberBranch); } else { TreeNumberingServiceImpl.incrementLastChainCharacter(newNodeNumberBranch, charNumber++); } node.addChild(createNode(childEntityTreeNode, newNodeNumberBranch)); } return node; } protected class TreeEventPerformer { public void refresh(final String[] args) { // nothing interesting here } public void initialize(final String[] args) { if (rootNode != null) { addOpenedNode(rootNode.getId()); } setSelectedEntityId(null); requestRender(); requestUpdateState(); } public void initializeAfterBack(final String[] args) { if (rootNode != null) { addOpenedNode(rootNode.getId()); } requestRender(); requestUpdateState(); } public void selectEntity(final String[] args) { notifyEntityIdChangeListeners(parseSelectedIdForListeners(getSelectedEntityId())); } public void customAction(final String[] args) { } public void removeSelectedEntity(final String[] args) { EntityOpResult result = getDataDefinition().delete(selectedEntityId); if (result.isSuccessfull()) { setSelectedEntityId(null); addTranslatedMessage(translateMessage("deleteMessage"), MessageType.SUCCESS); requestRender(); requestUpdateState(); } else { copyMessages(result.getMessagesHolder().getGlobalErrors()); } copyGlobalMessages(result.getMessagesHolder().getGlobalMessages()); } public void save(final String[] args) { Object tree = getFieldValue(); if (tree == null) { return; } Entity entity = belongsToFieldDefinition.getDataDefinition().get(belongsToEntityId); entity.setField(belongsToFieldDefinition.getName(), tree); Entity afterSaveEntity = belongsToFieldDefinition.getDataDefinition().save(entity); if (afterSaveEntity.isValid()) { requestRender(); requestUpdateState(); addTranslatedMessage(translateMessage("saveMessage"), MessageType.SUCCESS); } } public void clear(final String[] args) { rootNode = null; requestRender(); requestUpdateState(); } } }