<commit_before>package org.sagebionetworks.repo.manager.trash;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.sagebionetworks.StackConfiguration;
import org.sagebionetworks.dynamo.dao.nodetree.NodeTreeQueryDao;
import org.sagebionetworks.repo.manager.AuthorizationManager;
import org.sagebionetworks.repo.manager.AuthorizationManagerUtil;
import org.sagebionetworks.repo.manager.NodeInheritanceManager;
import org.sagebionetworks.repo.manager.NodeManager;
import org.sagebionetworks.repo.model.ACCESS_TYPE;
import org.sagebionetworks.repo.model.AccessControlListDAO;
import org.sagebionetworks.repo.model.DatastoreException;
import org.sagebionetworks.repo.model.EntityHeader;
import org.sagebionetworks.repo.model.Node;
import org.sagebionetworks.repo.model.NodeDAO;
import org.sagebionetworks.repo.model.ObjectType;
import org.sagebionetworks.repo.model.QueryResults;
import org.sagebionetworks.repo.model.TrashedEntity;
import org.sagebionetworks.repo.model.UnauthorizedException;
import org.sagebionetworks.repo.model.UserInfo;
import org.sagebionetworks.repo.model.dao.TrashCanDao;
import org.sagebionetworks.repo.model.jdo.KeyFactory;
import org.sagebionetworks.repo.model.message.ChangeType;
import org.sagebionetworks.repo.model.message.TransactionalMessenger;
import org.sagebionetworks.repo.web.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class TrashManagerImpl implements TrashManager {

	@Autowired
	private AuthorizationManager authorizationManager;

	@Autowired
	private NodeManager nodeManager;

	@Autowired
	private NodeInheritanceManager nodeInheritanceManager;

	@Autowired
	private NodeDAO nodeDao;

	@Autowired
	private AccessControlListDAO aclDAO;

	@Autowired
	private NodeTreeQueryDao nodeTreeQueryDao;

	@Autowired
	private TrashCanDao trashCanDao;
	
	@Autowired
	private TransactionalMessenger transactionalMessenger;

	@Autowired
	private StackConfiguration stackConfig;

	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public void moveToTrash(final UserInfo currentUser, final String nodeId)
			throws NotFoundException, DatastoreException, UnauthorizedException {

		if (currentUser == null) {
			throw new IllegalArgumentException("Current user cannot be null");
		}
		if (nodeId == null) {
			throw new IllegalArgumentException("Node ID cannot be null");
		}

		// Authorize
		UserInfo.validateUserInfo(currentUser);
		String userName = currentUser.getId().toString();
		AuthorizationManagerUtil.checkAuthorizationAndThrowException(
				authorizationManager.canAccess(currentUser, nodeId, ObjectType.ENTITY, ACCESS_TYPE.DELETE));

		// Whether it is too big for the trash can
		if (stackConfig.getDynamoEnabled()) {
			List<String> idList = nodeTreeQueryDao.getDescendants(
					KeyFactory.stringToKey(nodeId).toString(), TrashConstants.MAX_TRASHABLE + 1, null);
			if (idList != null && idList.size() > TrashConstants.MAX_TRASHABLE) {
				throw new TooBigForTrashcanException(
						"Too big to fit into trashcan. Entity " + nodeId +
						" has more than " + TrashConstants.MAX_TRASHABLE + " descendants. " +
						" Possible actions include: " +
						" 1) deleting the child entities first before deleting this entity, " +
						" 2) permanently deleting this entity instead of moving it to the trashcan.");
			}
		}

		// Move the node to the trash can folder
		Node node = nodeDao.getNode(nodeId);
		// Save before we reset the name and the parent
		final String oldNodeName = node.getName();
		final String oldParentId = node.getParentId();
		// Set the name to its ID to guarantee the entities in the trash folder all have unique names (PLFM-1760)
		node.setName(node.getId());
		final String trashCanId = KeyFactory.keyToString(TrashConstants.TRASH_FOLDER_ID);
		node.setParentId(trashCanId);
		nodeManager.updateForTrashCan(currentUser, node, ChangeType.DELETE);

		// Update the trash can table
		String userGroupId = currentUser.getId().toString();
		trashCanDao.create(userGroupId, nodeId, oldNodeName, oldParentId);

		// For all the descendants, we need to add them to the trash can table
		// and send delete messages to 2nd indices
		Collection<String> descendants = new ArrayList<String>();
		getDescendants(nodeId, descendants);
		for (String descendantId : descendants) {
			final EntityHeader entityHeader =  nodeDao.getEntityHeader(descendantId, null);
			final String nodeName = entityHeader.getName();
			final String parentId = nodeDao.getParentId(descendantId);
			trashCanDao.create(userGroupId, descendantId, nodeName, parentId);
			String etag = nodeDao.peekCurrentEtag(descendantId);
			transactionalMessenger.sendMessageAfterCommit(descendantId, ObjectType.ENTITY, etag, parentId, ChangeType.DELETE);
		}
		transactionalMessenger.sendModificationMessageAfterCommit( nodeId,ObjectType.ENTITY);
	}

	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public void restoreFromTrash(UserInfo currentUser, String nodeId, String newParentId)
			throws NotFoundException, DatastoreException, UnauthorizedException {

		if (currentUser == null) {
			throw new IllegalArgumentException("Current user cannot be null");
		}
		if (nodeId == null) {
			throw new IllegalArgumentException("Node ID cannot be null");
		}
		
		// Make sure the node is in the trash can
		final TrashedEntity trash = trashCanDao.getTrashedEntity(nodeId);
		if (trash == null) {
			throw new NotFoundException("The node " + nodeId + " is not in the trash can.");
		}

		// Make sure the node was indeed deleted by the user
		UserInfo.validateUserInfo(currentUser);
		final String userId = currentUser.getId().toString();
		final String deletedBy = trash.getDeletedByPrincipalId();
		if (!currentUser.isAdmin() && !deletedBy.equals(userId)) {
			throw new UnauthorizedException("User " + userId + " not allowed to restore "
					+ nodeId + ". The node was deleted by a different user.");
		}

		// Restore to its original parent if a new parent is not given
		if (newParentId == null) {
			newParentId = trash.getOriginalParentId();
		}
		
		// Make sure new parent is not in trash can.
		if (trashCanDao.getTrashedEntity(newParentId) != null) {
			throw new ParentInTrashCanException("The new parent " + newParentId + " is in the trash can and cannot be restored to.");
		}

		// Authorize on the new parent
		String userName = currentUser.getId().toString();
		AuthorizationManagerUtil.checkAuthorizationAndThrowException(
				authorizationManager.canAccess(currentUser, newParentId, ObjectType.ENTITY, ACCESS_TYPE.CREATE));
		Node node = nodeDao.getNode(nodeId);
		AuthorizationManagerUtil.checkAuthorizationAndThrowException(
				authorizationManager.canUserMoveRestrictedEntity(currentUser, trash.getOriginalParentId(), newParentId));

		// Now restore
		node.setName(trash.getEntityName());
		node.setParentId(newParentId);
		nodeManager.updateForTrashCan(currentUser, node, ChangeType.CREATE);

		// Update the trash can table
		trashCanDao.delete(deletedBy, nodeId);

		// For all the descendants, we need to remove them from the trash can table
		// and send delete messages to 2nd indices
		Collection<String> descendants = new ArrayList<String>();
		getDescendants(nodeId, descendants);
		for (String descendantId : descendants) {
			// Remove from the trash can table<commit_msg>Changed wording of backend error message when parent was in trash.<commit_after>package org.sagebionetworks.repo.manager.trash;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.sagebionetworks.StackConfiguration;
import org.sagebionetworks.dynamo.dao.nodetree.NodeTreeQueryDao;
import org.sagebionetworks.repo.manager.AuthorizationManager;
import org.sagebionetworks.repo.manager.AuthorizationManagerUtil;
import org.sagebionetworks.repo.manager.NodeInheritanceManager;
import org.sagebionetworks.repo.manager.NodeManager;
import org.sagebionetworks.repo.model.ACCESS_TYPE;
import org.sagebionetworks.repo.model.AccessControlListDAO;
import org.sagebionetworks.repo.model.DatastoreException;
import org.sagebionetworks.repo.model.EntityHeader;
import org.sagebionetworks.repo.model.Node;
import org.sagebionetworks.repo.model.NodeDAO;
import org.sagebionetworks.repo.model.ObjectType;
import org.sagebionetworks.repo.model.QueryResults;
import org.sagebionetworks.repo.model.TrashedEntity;
import org.sagebionetworks.repo.model.UnauthorizedException;
import org.sagebionetworks.repo.model.UserInfo;
import org.sagebionetworks.repo.model.dao.TrashCanDao;
import org.sagebionetworks.repo.model.jdo.KeyFactory;
import org.sagebionetworks.repo.model.message.ChangeType;
import org.sagebionetworks.repo.model.message.TransactionalMessenger;
import org.sagebionetworks.repo.web.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class TrashManagerImpl implements TrashManager {

	@Autowired
	private AuthorizationManager authorizationManager;

	@Autowired
	private NodeManager nodeManager;

	@Autowired
	private NodeInheritanceManager nodeInheritanceManager;

	@Autowired
	private NodeDAO nodeDao;

	@Autowired
	private AccessControlListDAO aclDAO;

	@Autowired
	private NodeTreeQueryDao nodeTreeQueryDao;

	@Autowired
	private TrashCanDao trashCanDao;
	
	@Autowired
	private TransactionalMessenger transactionalMessenger;

	@Autowired
	private StackConfiguration stackConfig;

	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public void moveToTrash(final UserInfo currentUser, final String nodeId)
			throws NotFoundException, DatastoreException, UnauthorizedException {

		if (currentUser == null) {
			throw new IllegalArgumentException("Current user cannot be null");
		}
		if (nodeId == null) {
			throw new IllegalArgumentException("Node ID cannot be null");
		}

		// Authorize
		UserInfo.validateUserInfo(currentUser);
		String userName = currentUser.getId().toString();
		AuthorizationManagerUtil.checkAuthorizationAndThrowException(
				authorizationManager.canAccess(currentUser, nodeId, ObjectType.ENTITY, ACCESS_TYPE.DELETE));

		// Whether it is too big for the trash can
		if (stackConfig.getDynamoEnabled()) {
			List<String> idList = nodeTreeQueryDao.getDescendants(
					KeyFactory.stringToKey(nodeId).toString(), TrashConstants.MAX_TRASHABLE + 1, null);
			if (idList != null && idList.size() > TrashConstants.MAX_TRASHABLE) {
				throw new TooBigForTrashcanException(
						"Too big to fit into trashcan. Entity " + nodeId +
						" has more than " + TrashConstants.MAX_TRASHABLE + " descendants. " +
						" Possible actions include: " +
						" 1) deleting the child entities first before deleting this entity, " +
						" 2) permanently deleting this entity instead of moving it to the trashcan.");
			}
		}

		// Move the node to the trash can folder
		Node node = nodeDao.getNode(nodeId);
		// Save before we reset the name and the parent
		final String oldNodeName = node.getName();
		final String oldParentId = node.getParentId();
		// Set the name to its ID to guarantee the entities in the trash folder all have unique names (PLFM-1760)
		node.setName(node.getId());
		final String trashCanId = KeyFactory.keyToString(TrashConstants.TRASH_FOLDER_ID);
		node.setParentId(trashCanId);
		nodeManager.updateForTrashCan(currentUser, node, ChangeType.DELETE);

		// Update the trash can table
		String userGroupId = currentUser.getId().toString();
		trashCanDao.create(userGroupId, nodeId, oldNodeName, oldParentId);

		// For all the descendants, we need to add them to the trash can table
		// and send delete messages to 2nd indices
		Collection<String> descendants = new ArrayList<String>();
		getDescendants(nodeId, descendants);
		for (String descendantId : descendants) {
			final EntityHeader entityHeader =  nodeDao.getEntityHeader(descendantId, null);
			final String nodeName = entityHeader.getName();
			final String parentId = nodeDao.getParentId(descendantId);
			trashCanDao.create(userGroupId, descendantId, nodeName, parentId);
			String etag = nodeDao.peekCurrentEtag(descendantId);
			transactionalMessenger.sendMessageAfterCommit(descendantId, ObjectType.ENTITY, etag, parentId, ChangeType.DELETE);
		}
		transactionalMessenger.sendModificationMessageAfterCommit( nodeId,ObjectType.ENTITY);
	}

	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	@Override
	public void restoreFromTrash(UserInfo currentUser, String nodeId, String newParentId)
			throws NotFoundException, DatastoreException, UnauthorizedException {

		if (currentUser == null) {
			throw new IllegalArgumentException("Current user cannot be null");
		}
		if (nodeId == null) {
			throw new IllegalArgumentException("Node ID cannot be null");
		}
		
		// Make sure the node is in the trash can
		final TrashedEntity trash = trashCanDao.getTrashedEntity(nodeId);
		if (trash == null) {
			throw new NotFoundException("The node " + nodeId + " is not in the trash can.");
		}

		// Make sure the node was indeed deleted by the user
		UserInfo.validateUserInfo(currentUser);
		final String userId = currentUser.getId().toString();
		final String deletedBy = trash.getDeletedByPrincipalId();
		if (!currentUser.isAdmin() && !deletedBy.equals(userId)) {
			throw new UnauthorizedException("User " + userId + " not allowed to restore "
					+ nodeId + ". The node was deleted by a different user.");
		}

		// Restore to its original parent if a new parent is not given
		if (newParentId == null) {
			newParentId = trash.getOriginalParentId();
		}
		
		// Make sure new parent is not in trash can.
		if (trashCanDao.getTrashedEntity(newParentId) != null) {
			throw new ParentInTrashCanException("The intended parent " + newParentId + " is in the trash can and cannot be restored to.");
		}

		// Authorize on the new parent
		String userName = currentUser.getId().toString();
		AuthorizationManagerUtil.checkAuthorizationAndThrowException(
				authorizationManager.canAccess(currentUser, newParentId, ObjectType.ENTITY, ACCESS_TYPE.CREATE));
		Node node = nodeDao.getNode(nodeId);
		AuthorizationManagerUtil.checkAuthorizationAndThrowException(
				authorizationManager.canUserMoveRestrictedEntity(currentUser, trash.getOriginalParentId(), newParentId));

		// Now restore
		node.setName(trash.getEntityName());
		node.setParentId(newParentId);
		nodeManager.updateForTrashCan(currentUser, node, ChangeType.CREATE);

		// Update the trash can table
		trashCanDao.delete(deletedBy, nodeId);

		// For all the descendants, we need to remove them from the trash can table
		// and send delete messages to 2nd indices
		Collection<String> descendants = new ArrayList<String>();
		getDescendants(nodeId, descendants);
		for (String descendantId : descendants) {
			// Remove from the trash can table
{'fraction_non_alphanumeric': 0.08805734578455873, 'fraction_numerical': 0.0011837432592397739, 'mean_word_length': 5.2272727272727275, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6966703', 'n_tokens_mistral': 4782, 'n_tokens_neox': 4478, 'n_words': 1141}