/* * Copyright 2014 Ranjan Kumar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.restfiddle.controller.rest; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.restfiddle.constant.NodeType; import com.restfiddle.controller.util.NodeUtil; import com.restfiddle.dao.ConversationRepository; import com.restfiddle.dao.GenericEntityRepository; import com.restfiddle.dao.NodeRepository; import com.restfiddle.dao.ProjectRepository; import com.restfiddle.dao.TagRepository; import com.restfiddle.dao.util.TreeNodeBuilder; import com.restfiddle.dto.NodeDTO; import com.restfiddle.dto.TagDTO; import com.restfiddle.entity.BaseNode; import com.restfiddle.entity.Conversation; import com.restfiddle.entity.GenericEntity; import com.restfiddle.entity.Project; import com.restfiddle.entity.Tag; import com.restfiddle.util.EntityToDTO; import com.restfiddle.util.TreeNode; @RestController @EnableAutoConfiguration @ComponentScan @Transactional public class NodeController { private static final String PROJECT = "PROJECT"; Logger logger = LoggerFactory.getLogger(NodeController.class); @Autowired private ProjectRepository projectRepository; @Autowired private NodeRepository nodeRepository; @Autowired private TagRepository tagRepository; @Autowired private ConversationRepository conversationRepository; @Autowired private GenericEntityRepository genericEntityRepository; @Autowired private GenerateApiController generateApiController; // Note : Creating a node requires parentId. Project-node is the root node and it is created during project creation. @RequestMapping(value = "/api/nodes/{parentId}/children", method = RequestMethod.POST, headers = "Accept=application/json") public @ResponseBody NodeDTO create(@PathVariable("parentId") String parentId, @RequestBody NodeDTO nodeDTO) { logger.debug("Creating a new node with information: " + nodeDTO); BaseNode node = new BaseNode(); node.setName(nodeDTO.getName()); node.setDescription(nodeDTO.getDescription()); node.setNodeType(nodeDTO.getNodeType()); node.setStarred(nodeDTO.getStarred()); BaseNode parentNode = nodeRepository.findOne(parentId); node.setWorkspaceId(parentNode.getWorkspaceId()); node.setParentId(parentId); // To find the last child node and its position long lastChildPosition = NodeUtil.findLastChildPosition(nodeRepository.getChildren(parentId)); // Set the appropriate node position. node.setPosition(lastChildPosition + 1); node = nodeRepository.save(node); if (nodeDTO.getConversationDTO() != null && nodeDTO.getConversationDTO().getId() != null) { Conversation conversation = conversationRepository.findOne(nodeDTO.getConversationDTO().getId()); node.setConversation(conversation); conversation.setNodeId(node.getId()); conversationRepository.save(conversation); } if (nodeDTO.getGenericEntityDTO() != null && nodeDTO.getGenericEntityDTO().getId() != null) { GenericEntity genericEntity = genericEntityRepository.findOne(nodeDTO.getGenericEntityDTO().getId()); genericEntity.setBaseNodeId(node.getId()); genericEntityRepository.save(genericEntity); node.setGenericEntity(genericEntity); } Project project = projectRepository.findOne(nodeDTO.getProjectId()); node.setProjectId(project.getId()); BaseNode savedNode = nodeRepository.save(node); // set tags List<Tag> tags = new ArrayList<Tag>(); List<TagDTO> tagDTOs = nodeDTO.getTags(); if (tagDTOs != null && !tagDTOs.isEmpty()) { List<String> tagIds = new ArrayList<String>(); for (TagDTO tagDTO : tagDTOs) { tagIds.add(tagDTO.getId()); } tags = (List<Tag>) tagRepository.findAll(tagIds); } savedNode.setTags(tags); savedNode = nodeRepository.save(savedNode); // Generate APIs for Entity if (nodeDTO.getGenericEntityDTO() != null && nodeDTO.getGenericEntityDTO().getId() != null) { generateApiController.generateApi(savedNode); } return EntityToDTO.toDTO(savedNode); } @RequestMapping(value = "/api/nodes/{id}", method = RequestMethod.DELETE, headers = "Accept=application/json") public @ResponseBody void delete(@PathVariable("id") String id) { logger.debug("Deleting node with id: " + id); BaseNode nodeToDelete = nodeRepository.findOne(id); Long deletedNodePosition = nodeToDelete.getPosition(); deleteNodesRecursively(nodeToDelete); BaseNode parent = nodeRepository.findOne(nodeToDelete.getParentId()); List<BaseNode> children = nodeRepository.getChildren(parent.getId()); if (children != null && !children.isEmpty()) { for (BaseNode baseNode : children) { if (baseNode.getPosition() > deletedNodePosition) { baseNode.setPosition(baseNode.getPosition() - 1); nodeRepository.save(baseNode); } } } } @RequestMapping(value = "/api/nodes/{id}/copy", method = RequestMethod.POST, headers = "Accept=application/json") public @ResponseBody void copy(@PathVariable("id") String id, @RequestBody NodeDTO nodeDTO) { BaseNode node = nodeRepository.findOne(id); node.setName(nodeDTO.getName()); node.setDescription(nodeDTO.getDescription()); copyNodesRecursively(node, node.getParentId()); } public void copyNodesRecursively(BaseNode node, String parentId) { NodeDTO dto = EntityToDTO.toDTO(node); NodeDTO newNode = create(parentId, dto); String nodeType = node.getNodeType(); if (nodeType != null && (NodeType.FOLDER.name().equalsIgnoreCase(nodeType) || NodeType.PROJECT.name().equalsIgnoreCase(nodeType) || NodeType.ENTITY.name() .equalsIgnoreCase(nodeType))) { List<BaseNode> children = getChildren(node.getId()); if (children != null && !children.isEmpty()) { for (BaseNode childNode : children) { copyNodesRecursively(childNode, newNode.getId()); } } } // This is just a workaround added for now. if (nodeType != null && NodeType.FOLDER.name().equalsIgnoreCase(nodeType)) { if (node.getGenericEntity() != null) { // TODO : genericEntityRepository.delete(node.getGenericEntity()); } } else if (nodeType != null && NodeType.ENTITY.name().equalsIgnoreCase(nodeType)) { // TODO : genericEntityRepository.delete(node.getGenericEntity()); } } public void deleteNodesRecursively(BaseNode node) { String nodeType = node.getNodeType(); if (nodeType != null && (NodeType.FOLDER.name().equalsIgnoreCase(nodeType) || NodeType.PROJECT.name().equalsIgnoreCase(nodeType) || NodeType.ENTITY.name() .equalsIgnoreCase(nodeType))) { List<BaseNode> children = getChildren(node.getId()); if (children != null && !children.isEmpty()) { for (BaseNode childNode : children) { deleteNodesRecursively(childNode); } } } // This is just a workaround added for now. if (nodeType != null && NodeType.FOLDER.name().equalsIgnoreCase(nodeType)) { if (node.getGenericEntity() != null) { genericEntityRepository.delete(node.getGenericEntity()); } } else if (nodeType != null && NodeType.ENTITY.name().equalsIgnoreCase(nodeType)) { genericEntityRepository.delete(node.getGenericEntity()); } nodeRepository.delete(node); } @RequestMapping(value = "/api/nodes", method = RequestMethod.GET) public @ResponseBody List<BaseNode> findAll() { logger.debug("Finding all nodes"); return nodeRepository.findAll(); } @RequestMapping(value = "/api/nodes/{id}", method = RequestMethod.GET) public @ResponseBody BaseNode findById(@PathVariable("id") String id) { logger.debug("Finding node by id: " + id); BaseNode baseNode = nodeRepository.findOne(id); baseNode.getConversation(); return baseNode; } @RequestMapping(value = "/api/nodes/{parentId}/children", method = RequestMethod.GET) public @ResponseBody List<BaseNode> getChildren(@PathVariable("parentId") String parentId) { logger.debug("Finding children nodes"); return nodeRepository.getChildren(parentId); } @RequestMapping(value = "/api/nodes/{id}", method = RequestMethod.PUT, headers = "Accept=application/json") public @ResponseBody BaseNode update(@PathVariable("id") String id, @RequestBody NodeDTO updated) { logger.debug("Updating node with information: " + updated); BaseNode node = nodeRepository.findOne(updated.getId()); if (updated.getName() != null) { node.setName(updated.getName()); } if (updated.getDescription() != null) { node.setDescription(updated.getDescription()); } if (updated.getStarred() != null) { node.setStarred(updated.getStarred()); } nodeRepository.save(node); return node; } public TreeNode getProjectTree(String id) { return getProjectTree(id, null, null); } // Get tree-structure for a project. Id parameter is the project-reference node-id. @RequestMapping(value = "/api/nodes/{id}/tree", method = RequestMethod.GET) public @ResponseBody TreeNode getProjectTree(@PathVariable("id") String id, @RequestParam(value = "search", required = false) String search, @RequestParam(value = "sort", required = false) String sort) { // Note : There must be a better way of doing it. This method is written in a hurry. // Get project Id from the reference node BaseNode projectRefNode = nodeRepository.findOne(id); String projectId = projectRefNode.getProjectId(); // Get the list of nodes for a project. List<BaseNode> listOfNodes = nodeRepository.searchNodesFromAProject(projectId, search != null ? search : ""); // Creating a map of nodes with node-id as key Map<String, BaseNode> baseNodeMap = new HashMap<String, BaseNode>(); Map<String, TreeNode> treeNodeMap = new HashMap<String, TreeNode>(); TreeNode rootNode = null; TreeNode treeNode; TreeNode parentTreeNode; String methodType = ""; for (BaseNode baseNode : listOfNodes) { String nodeId = baseNode.getId(); baseNodeMap.put(nodeId, baseNode); if (baseNode.getConversation() != null) { methodType = baseNode.getConversation().getRfRequest().getMethodType(); } treeNode = TreeNodeBuilder.createTreeNode(nodeId, baseNode.getName(), baseNode.getDescription(), baseNode.getWorkspaceId(), baseNode.getParentId(), baseNode.getPosition(), baseNode.getNodeType(), baseNode.getStarred(), methodType, baseNode.getLastModifiedDate(), baseNode.getLastModifiedBy()); treeNode.setProjectId(projectId); treeNodeMap.put(nodeId, treeNode); } for (BaseNode baseNode : listOfNodes) { String nodeId = baseNode.getId(); String parentId = baseNode.getParentId(); treeNode = treeNodeMap.get(nodeId); if (NodeType.PROJECT.name().equals(baseNode.getNodeType())) { // Identify root node for a project rootNode = treeNode; } else { // Build parent node parentTreeNode = treeNodeMap.get(parentId); // Set parent tree node treeNode.setParent(parentTreeNode); // Add child node to the parent parentTreeNode.getChildren().add(treeNode); } } if (search != null && !search.trim().equals("")) { for (BaseNode baseNode : listOfNodes) { if (baseNode.getNodeType() != null && !NodeType.PROJECT.name().equals(baseNode.getNodeType())) { TreeNode node = treeNodeMap.get(baseNode.getId()); if (node.getChildren().isEmpty()) { TreeNode parent = treeNodeMap.get(baseNode.getParentId()); parent.getChildren().remove(node); } } } } int order = 1; if (sort != null) { if (sort.trim().charAt(0) == '-') { order = -1; sort = sort.substring(1); } sortTree(rootNode, sort, order); }else{ sortTree(rootNode, "position", order); } return rootNode; } private void sortTree(TreeNode rootNode, String sort, final int order) { if (rootNode != null && rootNode.getChildren() != null) { Comparator<TreeNode> comparator; switch (sort) { case "lastModified": comparator = new Comparator<TreeNode>() { @Override public int compare(TreeNode o1, TreeNode o2) { int val = 0; if (o1.getLastModifiedDate() != null && o2.getLastModifiedDate() != null) { val = o1.getLastModifiedDate().compareTo(o2.getLastModifiedDate()); } else if (o1.getLastModifiedDate() != null) { val = 1; } else if (o2.getLastModifiedDate() != null) { val = -1; } return order * val; } }; break; case "name": comparator = new Comparator<TreeNode>() { @Override public int compare(TreeNode o1, TreeNode o2) { return order * o1.getName().compareTo(o2.getName()); } }; break; default: comparator = new Comparator<TreeNode>() { @Override public int compare(TreeNode o1, TreeNode o2) { return order * o1.getPosition().compareTo(o2.getPosition()); } }; break; } sortTreeNodes(rootNode, comparator); } } private void sortTreeNodes(TreeNode rootNode, Comparator<TreeNode> comparator) { if (rootNode != null && rootNode.getChildren() != null) { List<TreeNode> childs = rootNode.getChildren(); for (TreeNode node : childs) { sortTreeNodes(node, comparator); } Collections.sort(childs, comparator); if (!childs.isEmpty()) { rootNode.setLastModifiedDate(childs.get(0).getLastModifiedDate()); } } } @RequestMapping(value = "/api/workspaces/{workspaceId}/nodes/starred", method = RequestMethod.GET) public @ResponseBody List<NodeDTO> findStarredNodes(@PathVariable("workspaceId") String workspaceId, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "limit", required = false) Integer limit, @RequestParam(value = "search", required = false) String search, @RequestParam(value = "sortBy", required = false) String sortBy) { logger.debug("Finding starred nodes."); int pageNo = 0; if (page != null && page > 0) { pageNo = page; } int numberOfRecords = 10; if (limit != null && limit > 0) { numberOfRecords = limit; } Sort sort = new Sort(Direction.DESC, "lastModifiedDate"); if ("name".equals(sortBy)) { sort = new Sort(Direction.ASC, "name"); } else if ("lastRun".equals(sortBy)) { sort = new Sort(Direction.DESC, "lastModifiedDate"); } else if ("nameDesc".equals(sortBy)) { sort = new Sort(Direction.DESC, "name"); } Pageable pageable = new PageRequest(pageNo, numberOfRecords, sort); Page<BaseNode> paginatedStarredNodes = nodeRepository.findStarredNodes(workspaceId, search != null ? search : "", pageable); List<BaseNode> starredNodes = paginatedStarredNodes.getContent(); long totalElements = paginatedStarredNodes.getTotalElements(); List<NodeDTO> response = new ArrayList<NodeDTO>(); for (BaseNode item : starredNodes) { response.add(EntityToDTO.toDTO(item)); } System.out.println("totalElements : " + totalElements); return response; } @RequestMapping(value = "/api/nodes/{id}/tags", method = RequestMethod.POST, headers = "Accept=application/json") public @ResponseBody Boolean addTags(@PathVariable("id") String id, @RequestBody List<TagDTO> tagDTOs) { logger.debug("Adding the following tags: " + tagDTOs); BaseNode node = nodeRepository.findOne(id); List<Tag> tags = new ArrayList<Tag>(); if (tagDTOs != null && !tagDTOs.isEmpty()) { List<String> tagIds = new ArrayList<String>(); for (TagDTO tagDTO : tagDTOs) { tagIds.add(tagDTO.getId()); } tags = (List<Tag>) tagRepository.findAll(tagIds); } node.setTags(tags); nodeRepository.save(node); return Boolean.TRUE; } @RequestMapping(value = "/api/nodes/{id}/requests", method = RequestMethod.GET) public @ResponseBody List<BaseNode> getProjectRequests(@PathVariable("id") String id, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "limit", required = false) Integer limit, @RequestParam(value = "search", required = false) String search, @RequestParam(value = "sort", required = false) String sortBy) { // Note : There must be a better way of doing it. This method is written in a hurry. // Get project Id from the reference node BaseNode projectRefNode = nodeRepository.findOne(id); String projectId = projectRefNode.getProjectId(); List<BaseNode> requestsNodes = nodeRepository.findRequestsFromAProject(projectId, search != null ? search : ""); return requestsNodes; } @RequestMapping(value = "/api/nodes/{id}/move", method = RequestMethod.POST, headers = "Accept=application/json") public @ResponseBody void move(@PathVariable("id") String id, @RequestParam(value = "newRefNodeId", required = true) String newRefNodeId, @RequestParam(value = "position", required = true) String position) { BaseNode node = nodeRepository.findOne(id); Long oldPosition = node.getPosition(); BaseNode newRefNode = nodeRepository.findOne(newRefNodeId); BaseNode oldParentNode = nodeRepository.findOne(node.getParentId()); Long newPosition; String newParentId; if(position.equals("over")){ newParentId = newRefNode.getId(); newPosition = (long) 1; }else if(position.equals("before")){ newParentId = newRefNode.getParentId(); newPosition = newRefNode.getPosition(); }else{ newParentId = newRefNode.getParentId(); newPosition = newRefNode.getPosition()+1; } // Not allowed to save request under a request if (position.equals("over") && !(newRefNode.getNodeType().equalsIgnoreCase(PROJECT) || newRefNode.getNodeType().equalsIgnoreCase("FOLDER"))) { return; } // Special case where -1 getting saved for non-project node if (!(node.getNodeType() != null && node.getNodeType().equalsIgnoreCase(PROJECT)) && newParentId.equals("-1")){ return; } // update new folder List<BaseNode> newFolderChildren; if(newRefNode.getNodeType() != null && (newRefNode.getNodeType().equalsIgnoreCase(PROJECT) || newRefNode.getNodeType().equalsIgnoreCase("FOLDER"))){ newFolderChildren = nodeRepository.getChildren(newRefNode.getId()); }else{ newFolderChildren = nodeRepository.getChildren(newRefNode.getParentId()); } node.setParentId(newParentId); node.setPosition(newPosition); nodeRepository.save(node); for (BaseNode newFolderChild : newFolderChildren) { if (newFolderChild.getPosition() >= newPosition && newFolderChild.getId() != id) { newFolderChild.setPosition(newFolderChild.getPosition() + 1); nodeRepository.save(newFolderChild); } } //If node is moved within the same folder, updating new folder is sufficient if(oldParentNode.getId().equals(newParentId)){ return; } // update old folder List<BaseNode> oldFolderChildren = nodeRepository.getChildren(oldParentNode.getId()); if (oldFolderChildren != null && !oldFolderChildren.isEmpty()) { for (BaseNode oldFolderChild : oldFolderChildren) { if (oldFolderChild.getPosition() >= oldPosition) { oldFolderChild.setPosition(oldFolderChild.getPosition() - 1); nodeRepository.save(oldFolderChild); } } } } @RequestMapping(value = "/api/workspaces/{workspaceId}/projects", method = RequestMethod.GET) public @ResponseBody List<BaseNode> findProjectsFromAWorkspace(@PathVariable("workspaceId") String workspaceId, @RequestParam(value = "search", required = false) String search) { return nodeRepository.findProjectsfromAWorkspace(workspaceId, search != null ? search : ""); } }