Java tutorial
/* * Copyright 2014 EUROPEAN DYNAMICS SA <info@eurodyn.com> * * Licensed under the EUPL, Version 1.1 only (the "License"). You may not use this work except in * compliance with the Licence. You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/software/page/eupl/licence-eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence for the specific language governing permissions and limitations under * the Licence. */ package com.eurodyn.qlack2.fuse.cm.impl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.inject.Inject; import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.transaction.Transactional; import javax.transaction.Transactional.TxType; import org.joda.time.DateTime; import org.ops4j.pax.cdi.api.OsgiServiceProvider; import com.eurodyn.qlack2.fuse.cm.api.ConcurrencyControlService; import com.eurodyn.qlack2.fuse.cm.api.DocumentService; import com.eurodyn.qlack2.fuse.cm.api.VersionService; import com.eurodyn.qlack2.fuse.cm.api.dto.CreateFileAndVersionStatusDTO; import com.eurodyn.qlack2.fuse.cm.api.dto.FileDTO; import com.eurodyn.qlack2.fuse.cm.api.dto.FolderDTO; import com.eurodyn.qlack2.fuse.cm.api.dto.NodeDTO; import com.eurodyn.qlack2.fuse.cm.api.dto.VersionDTO; import com.eurodyn.qlack2.fuse.cm.api.exception.QAncestorFolderLockException; import com.eurodyn.qlack2.fuse.cm.api.exception.QDescendantNodeLockException; import com.eurodyn.qlack2.fuse.cm.api.exception.QFileNotFoundException; import com.eurodyn.qlack2.fuse.cm.api.exception.QIOException; import com.eurodyn.qlack2.fuse.cm.api.exception.QInvalidPathException; import com.eurodyn.qlack2.fuse.cm.api.exception.QNodeLockException; import com.eurodyn.qlack2.fuse.cm.api.exception.QSelectedNodeLockException; import com.eurodyn.qlack2.fuse.cm.impl.model.Node; import com.eurodyn.qlack2.fuse.cm.impl.model.NodeAttribute; import com.eurodyn.qlack2.fuse.cm.impl.model.NodeType; import com.eurodyn.qlack2.fuse.cm.impl.model.QNode; import com.eurodyn.qlack2.fuse.cm.impl.model.QNodeAttribute; import com.eurodyn.qlack2.fuse.cm.impl.util.Constants; import com.eurodyn.qlack2.fuse.cm.impl.util.ConverterUtil; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @Transactional @Singleton @OsgiServiceProvider(classes = { DocumentService.class }) public class DocumentServiceImpl implements DocumentService { private static final Logger LOGGER = Logger.getLogger(DocumentServiceImpl.class.getName()); @PersistenceContext(unitName = "fuse-contentmanager") private EntityManager em; @Inject private VersionService versionService; @Inject private ConcurrencyControlService concurrencyControlService; @Override @Transactional(TxType.REQUIRED) public String createFolder(FolderDTO folder, String userID, String lockToken) throws QNodeLockException { Node parent = null; if (folder.getParentId() != null) { parent = Node.findFolder(folder.getParentId(), em); } // Check for ancestor node (folder) lock conflicts. if (parent != null) { NodeDTO ancConflict = concurrencyControlService.getAncestorFolderWithLockConflict(parent.getId(), lockToken); // In case a conflict was found an exception is thrown if (ancConflict != null && ancConflict.getId() != null) { throw new QAncestorFolderLockException( "An ancestor folder is locked" + " and an" + " invalid lock token was passed; the folder cannot be created.", ancConflict.getId(), ancConflict.getName()); } } Node folderEntity = ConverterUtil.nodeDTOToNode(folder); folderEntity.setParent(parent); // Set created / last modified information DateTime now = DateTime.now(); folderEntity.setCreatedOn(now.getMillis()); folderEntity.getAttributes().add(new NodeAttribute(Constants.ATTR_CREATED_BY, userID, folderEntity)); folderEntity.getAttributes().add( new NodeAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), folderEntity)); folderEntity.getAttributes().add(new NodeAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, folderEntity)); em.persist(folderEntity); return folderEntity.getId(); } @Override @Transactional(TxType.REQUIRED) public void deleteFolder(String folderID, String lockToken) throws QNodeLockException, QFileNotFoundException { Node node = Node.findNode(folderID, em); if (node == null) { throw new QFileNotFoundException("The folder you want to delete does not exist"); } // Check whether there is a lock conflict with the current node. NodeDTO selConflict = concurrencyControlService.getSelectedNodeWithLockConflict(folderID, lockToken); if (selConflict != null && selConflict.getName() != null) { throw new QSelectedNodeLockException( "The selected folder is locked" + " and an" + " invalid lock token was passed; the folder cannot be deleted.", selConflict.getId(), selConflict.getName()); } // Check for ancestor node (folder) lock conflicts. if (node.getParent() != null) { NodeDTO ancConflict = concurrencyControlService .getAncestorFolderWithLockConflict(node.getParent().getId(), lockToken); // In case a conflict was found an exception is thrown if (ancConflict != null && ancConflict.getId() != null) { throw new QAncestorFolderLockException( "An ancestor folder is locked" + " and an" + " invalid lock token was passed; the folder cannot be deleted.", ancConflict.getId(), ancConflict.getName()); } } // Check for descendant node lock conflicts NodeDTO desConflict = concurrencyControlService.getDescendantNodeWithLockConflict(folderID, lockToken); // In case a conflict was found an exception is thrown if (desConflict != null && desConflict.getId() != null) { throw new QDescendantNodeLockException( "An descendant node is locked" + " and an" + " invalid lock token was passed; the folder cannot be deleted.", desConflict.getId(), desConflict.getName()); } em.remove(node); } @Override @Transactional(TxType.REQUIRED) public void renameFolder(String folderID, String newName, String userID, String lockToken) throws QNodeLockException, QFileNotFoundException { Node folder = Node.findFolder(folderID, em); if (folder == null) { throw new QFileNotFoundException("The folder you want to rename does not exist"); } if ((folder.getLockToken() != null) && (!folder.getLockToken().equals(lockToken))) { throw new QNodeLockException("Folder with ID " + folderID + " is locked and an " + "invalid lock token was passed; the folder cannot be renamed."); } folder.setAttribute(Constants.ATTR_NAME, newName, em); // Update last modified information if (userID != null) { DateTime now = DateTime.now(); folder.setAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), em); folder.setAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, em); } } @Override @Transactional(TxType.REQUIRED) public FolderDTO getFolderByID(String folderID, boolean lazyRelatives, boolean findPath) { return ConverterUtil.nodeToFolderDTO(Node.findFolder(folderID, em), lazyRelatives, findPath); } @Override @Transactional(TxType.REQUIRED) public byte[] getFolderAsZip(String folderID, boolean includeProperties, boolean isDeep) throws QFileNotFoundException { byte[] retVal = null; Node folder = Node.findFolder(folderID, em); if (folder == null) { throw new QFileNotFoundException("The folder you want to download does not exist"); } String nodeName = folder.getAttribute(Constants.ATTR_NAME).getValue(); boolean hasEntries = false; ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ZipOutputStream out = new ZipOutputStream(outStream); try { for (Node child : folder.getChildren()) { if (child.getType() == NodeType.FILE) { byte[] fileRetVal = versionService.getFileAsZip(child.getId(), includeProperties); if (fileRetVal != null) { hasEntries = true; ZipEntry entry = new ZipEntry(child.getAttribute(Constants.ATTR_NAME).getValue() + ".zip"); out.putNextEntry(entry); out.write(fileRetVal, 0, fileRetVal.length); } } else if ((child.getType() == NodeType.FOLDER) && isDeep) { byte[] folderRetVal = getFolderAsZip(child.getId(), includeProperties, isDeep); if (folderRetVal != null) { hasEntries = true; ZipEntry entry = new ZipEntry(child.getAttribute(Constants.ATTR_NAME).getValue() + ".zip"); out.putNextEntry(entry); out.write(folderRetVal, 0, folderRetVal.length); } } } if (includeProperties) { hasEntries = true; ZipEntry entry = new ZipEntry(nodeName + ".properties"); out.putNextEntry(entry); StringBuilder buf = new StringBuilder(); // Include a created on property buf.append(Constants.CREATED_ON).append(" = ").append(folder.getCreatedOn()).append("\n"); for (NodeAttribute attribute : folder.getAttributes()) { buf.append(attribute.getName()); buf.append(" = "); buf.append(attribute.getValue()); buf.append("\n"); } out.write(buf.toString().getBytes()); } if (hasEntries) { out.close(); retVal = outStream.toByteArray(); } } catch (IOException ex) { LOGGER.log(Level.SEVERE, ex.getLocalizedMessage(), ex); throw new QIOException("Error writing ZIP for folder with ID " + folderID); } return retVal; } @Override @Transactional(TxType.REQUIRED) public String createFile(FileDTO file, String userID, String lockToken) throws QNodeLockException { Node parent = null; if (file.getParentId() != null) { parent = Node.findFolder(file.getParentId(), em); } // Check for ancestor node (folder) lock conflicts. if (parent != null) { NodeDTO ancConflict = concurrencyControlService.getAncestorFolderWithLockConflict(parent.getId(), lockToken); // In case a conflict was found an exception is thrown if (ancConflict != null && ancConflict.getId() != null) { throw new QAncestorFolderLockException( "An ancestor folder is locked" + " and an" + " invalid lock token was passed; the file cannot be created.", ancConflict.getId(), ancConflict.getName()); } } Node fileEntity = ConverterUtil.nodeDTOToNode(file); fileEntity.setParent(parent); // fileEntity.setMimetype(file.getMimetype()); fileEntity.setContentSize(file.getSize()); // Set created / last modified information DateTime now = DateTime.now(); fileEntity.setCreatedOn(now.getMillis()); fileEntity.getAttributes().add(new NodeAttribute(Constants.ATTR_CREATED_BY, userID, fileEntity)); fileEntity.getAttributes().add( new NodeAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), fileEntity)); fileEntity.getAttributes().add(new NodeAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, fileEntity)); em.persist(fileEntity); return fileEntity.getId(); } @Override @Transactional(TxType.REQUIRED) public CreateFileAndVersionStatusDTO createFileAndVersion(FileDTO file, VersionDTO cmVersion, byte[] content, String userID, String lockToken) throws QNodeLockException, QFileNotFoundException { // Prepare the returnVal CreateFileAndVersionStatusDTO status = new CreateFileAndVersionStatusDTO(); String newFileID = this.createFile(file, userID, lockToken); status.setFileID(newFileID); // Create a new Version for the specific file status.setVersionID( versionService.createVersion(newFileID, cmVersion, file.getName(), content, userID, lockToken)); em.flush(); return status; } @Override @Transactional(TxType.REQUIRED) public void deleteFile(String fileID, String lockToken) throws QNodeLockException, QFileNotFoundException { Node node = Node.findFile(fileID, em); if (node == null) { throw new QFileNotFoundException("The file to delete does not exist"); } // Check whether there is a lock conflict with the current node. NodeDTO selConflict = concurrencyControlService.getSelectedNodeWithLockConflict(fileID, lockToken); if (selConflict != null && selConflict.getName() != null) { throw new QSelectedNodeLockException( "The selected file is locked" + " and an" + " invalid lock token was passed; the file cannot be deleted.", selConflict.getId(), selConflict.getName()); } // Check for ancestor node (folder) lock conflicts. if (node.getParent() != null) { NodeDTO ancConflict = concurrencyControlService .getAncestorFolderWithLockConflict(node.getParent().getId(), lockToken); // In case a conflict was found an exception is thrown if (ancConflict != null && ancConflict.getId() != null) { throw new QAncestorFolderLockException( "An ancestor folder is locked" + " and an" + " invalid lock token was passed; the file cannot be deleted.", ancConflict.getId(), ancConflict.getName()); } } em.remove(node); em.flush(); } @Override @Transactional(TxType.REQUIRED) public void renameFile(String fileID, String newName, String userID, String lockToken) throws QNodeLockException, QFileNotFoundException { Node file = Node.findFile(fileID, em); if (file == null) { throw new QFileNotFoundException("The file to rename does not exist."); } if ((file.getLockToken() != null) && (!file.getLockToken().equals(lockToken))) { throw new QNodeLockException("File with ID " + fileID + " is locked and an " + "invalid lock token was passed; the file cannot be renamed."); } file.setAttribute(Constants.ATTR_NAME, newName, em); // Update last modified information if (userID != null) { DateTime now = DateTime.now(); file.setAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), em); file.setAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, em); } } @Override @Transactional(TxType.REQUIRED) public FileDTO getFileByID(String fileID, boolean includeVersions, boolean findPath) { FileDTO retVal = ConverterUtil.nodeToFileDTO(Node.findFile(fileID, em), false, findPath); if (includeVersions) { retVal.setVersions(versionService.getFileVersions(fileID)); } return retVal; } @Override @Transactional(TxType.REQUIRED) public NodeDTO getNodeByID(String nodeID) { return ConverterUtil.nodeToNodeDTO(Node.findNode(nodeID, em)); } @Override @Transactional(TxType.REQUIRED) public List<NodeDTO> getNodeByAttributes(String parentId, Map<String, String> attributes) { StringBuilder sbQuery = new StringBuilder("SELECT n FROM Node n "); if (attributes != null && !attributes.isEmpty()) { int i = 0; for (@SuppressWarnings("unused") Map.Entry<String, String> entry : attributes.entrySet()) { i++; sbQuery.append("INNER JOIN n.attributes attr").append(i).append(" WITH ( "); sbQuery.append("attr").append(i).append(".name = :attr_").append(i).append(" AND attr").append(i) .append(".value = :value_").append(i).append(")"); } } sbQuery.append(" WHERE n.parent.id = :parentId ORDER BY n.createdOn ASC"); Query query = em.createQuery(sbQuery.toString()); query.setParameter("parentId", parentId); if (attributes != null) { int i = 0; for (Map.Entry<String, String> entry : attributes.entrySet()) { i++; query.setParameter("attr_" + i, entry.getKey()); query.setParameter("value_" + i, entry.getValue()); } } @SuppressWarnings("unchecked") List<Node> nodes = query.getResultList(); return ConverterUtil.nodeToNodeDTOList(nodes); } @Override @Transactional(TxType.REQUIRED) public FolderDTO getParent(String nodeID, boolean lazyRelatives) { Node node = Node.findNode(nodeID, em); return ConverterUtil.nodeToFolderDTO(node.getParent(), lazyRelatives, false); } @Override @Transactional(TxType.REQUIRED) public List<FolderDTO> getAncestors(String nodeID) { List<FolderDTO> retVal = null; Node node = Node.findNode(nodeID, em); if (node.getParent() == null) { return new ArrayList<>(); } else { retVal = getAncestors(node.getParent().getId()); } retVal.add(ConverterUtil.nodeToFolderDTO(node.getParent(), true, false)); return retVal; } @Override @Transactional(TxType.REQUIRED) public String createAttribute(String nodeId, String attributeName, String attributeValue, String userId, String lockToken) throws QNodeLockException, QFileNotFoundException { Node node = Node.findNode(nodeId, em); if (node == null) { throw new QFileNotFoundException("The node, which attribute should be created does not exist."); } if ((node.getLockToken() != null) && (!node.getLockToken().equals(lockToken))) { throw new QNodeLockException("Node with ID " + nodeId + " is locked and an " + "invalid lock token was passed; the file attributes cannot be updated."); } NodeAttribute attribute = new NodeAttribute(attributeName, attributeValue, node); node.getAttributes().add(attribute); // Set created / last modified information if (userId != null) { DateTime now = DateTime.now(); node.setCreatedOn(now.getMillis()); node.setAttribute(Constants.ATTR_CREATED_BY, userId, em); node.setAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), em); node.setAttribute(Constants.ATTR_LAST_MODIFIED_BY, userId, em); } return attribute.getId(); } @Override @Transactional(TxType.REQUIRED) public void updateAttribute(String nodeID, String attributeName, String attributeValue, String userID, String lockToken) throws QNodeLockException, QFileNotFoundException { Node node = Node.findNode(nodeID, em); if (node == null) { throw new QFileNotFoundException("The node, which attribute should be updated does not exist."); } if ((node.getLockToken() != null) && (!node.getLockToken().equals(lockToken))) { throw new QNodeLockException("Node with ID " + nodeID + " is locked and an " + "invalid lock token was passed; the file attributes cannot be updated."); } node.setAttribute(attributeName, attributeValue, em); // Update last modified information if (userID != null) { DateTime now = DateTime.now(); node.setAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), em); node.setAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, em); } } @Override @Transactional(TxType.REQUIRED) public void updateAttributes(String nodeID, Map<String, String> attributes, String userID, String lockToken) throws QNodeLockException, QFileNotFoundException { Node node = Node.findNode(nodeID, em); if (node == null) { throw new QFileNotFoundException("The node to update does not exist."); } if ((node.getLockToken() != null) && (!node.getLockToken().equals(lockToken))) { throw new QNodeLockException("Node with ID " + nodeID + " is locked and an " + "invalid lock token was passed; the file attributes cannot be updated."); } for (String attributeName : attributes.keySet()) { node.setAttribute(attributeName, attributes.get(attributeName), em); } // Update last modified information if (userID != null) { DateTime now = DateTime.now(); node.setAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), em); node.setAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, em); } } @Override @Transactional(TxType.REQUIRED) public void deleteAttribute(String nodeID, String attributeName, String userID, String lockToken) throws QNodeLockException, QFileNotFoundException { Node node = Node.findNode(nodeID, em); if (node == null) { throw new QFileNotFoundException("The node, which attributes should be deleted does not exist."); } if ((node.getLockToken() != null) && (!node.getLockToken().equals(lockToken))) { throw new QNodeLockException("Node with ID " + nodeID + " is locked and an " + "invalid lock token was passed; the file attributes cannot be deleted."); } node.removeAttribute(attributeName, em); // Update last modified information if (userID != null) { DateTime now = DateTime.now(); node.setAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), em); node.setAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, em); } } @Override @Transactional(TxType.REQUIRED) public String copy(String nodeID, String newParentID, String userID, String lockToken) throws QFileNotFoundException { Node node = Node.findNode(nodeID, em); if (node == null) { throw new QFileNotFoundException("The node, to be copied does not exist."); } Node newParent = Node.findFolder(newParentID, em); if ((newParent.getLockToken() != null) && (!newParent.getLockToken().equals(lockToken))) { throw new QNodeLockException("Node with ID " + newParentID + " is locked and an " + "invalid lock token was passed; a new node cannot be copied into it."); } checkCyclicPath(nodeID, newParent); return copyNode(node, newParent, userID); } private String copyNode(Node node, Node newParent, String userID) { Node newNode = new Node(); newNode.setType(node.getType()); newNode.setParent(newParent); List<NodeAttribute> newAttributes = new ArrayList<>(); newNode.setAttributes(newAttributes); // Copy attributes except created/modified/locked information for (NodeAttribute attribute : node.getAttributes()) { switch (attribute.getName()) { case Constants.ATTR_CREATED_BY: case Constants.ATTR_LAST_MODIFIED_BY: case Constants.ATTR_LAST_MODIFIED_ON: case Constants.ATTR_LOCKED_BY: case Constants.ATTR_LOCKED_ON: break; default: newNode.getAttributes().add(new NodeAttribute(attribute.getName(), attribute.getValue(), newNode)); break; } } // Set created / last modified information DateTime now = DateTime.now(); newNode.setCreatedOn(now.getMillis()); newNode.getAttributes().add(new NodeAttribute(Constants.ATTR_CREATED_BY, userID, newNode)); newNode.getAttributes() .add(new NodeAttribute(Constants.ATTR_LAST_MODIFIED_ON, String.valueOf(now.getMillis()), newNode)); newNode.getAttributes().add(new NodeAttribute(Constants.ATTR_LAST_MODIFIED_BY, userID, newNode)); em.persist(newNode); for (Node child : node.getChildren()) { copyNode(child, newNode, userID); } return newNode.getId(); } @Override @Transactional(TxType.REQUIRED) public void move(String nodeID, String newParentID, String userID, String lockToken) throws QFileNotFoundException { Node node = Node.findNode(nodeID, em); if (node == null) { throw new QFileNotFoundException("The node, to be moved does not exist."); } Node newParent = Node.findFolder(newParentID, em); if ((node.getLockToken() != null) && (!node.getLockToken().equals(lockToken))) { throw new QNodeLockException("Node with ID " + nodeID + " is locked and an " + "invalid lock token was passed; it cannot be moved."); } if ((newParent.getLockToken() != null) && (!newParent.getLockToken().equals(lockToken))) { throw new QNodeLockException("Node with ID " + newParentID + " is locked and an " + "invalid lock token was passed; a new node cannot be moved into it."); } checkCyclicPath(nodeID, newParent); node.setParent(newParent); } private void checkCyclicPath(String nodeID, Node newParent) { Node checkedNode = newParent; while (checkedNode != null) { if (checkedNode.getId().equals(nodeID)) { throw new QInvalidPathException("Cannot move node with ID " + nodeID + " under node with ID " + newParent.getId() + " since this will create a cyclic path."); } checkedNode = checkedNode.getParent(); } } @Override public boolean isFileNameUnique(String name, String parentNodeID) { QNode qNode = QNode.node; QNodeAttribute qNodeAttribute = new QNodeAttribute("nodeAttribute"); boolean isFileNameUnique = true; // Query retrieving the nodes (folder/file) which have a required // node attribute name. JPAQuery<Node> q = new JPAQueryFactory(em).selectFrom(qNode).innerJoin(qNode.attributes, qNodeAttribute) .where(qNode.parent.id.eq(parentNodeID).and(qNodeAttribute.name.eq(Constants.ATTR_NAME)) .and(qNodeAttribute.value.eq(name))); // Get the count of nodes which the specific name in odrer to find out // whether the name is not unique long count = q.fetchCount(); // In case the number is larger than zero it mean that the file name // already exist if (count > 0) { isFileNameUnique = false; } return isFileNameUnique; } @Override public List<String> duplicateFileNamesInDirectory(List<String> fileNames, String parentId) { QNode qNode = QNode.node; QNodeAttribute qNodeAttribute = new QNodeAttribute("nodeAttribute"); // Selects all the nodes that their name is contained in a list of // strings JPAQuery<Node> q = new JPAQueryFactory(em).selectFrom(qNode).innerJoin(qNode.attributes, qNodeAttribute) .where(qNode.parent.id.eq(parentId).and(qNodeAttribute.name.eq(Constants.ATTR_NAME)) .and(qNodeAttribute.value.in(fileNames))); List<Node> nodeResultList = q.fetchResults().getResults(); List<String> namesList = new ArrayList<>(); // TODO Future enhancement for (Node node : nodeResultList) { NodeDTO nodeDTO = ConverterUtil.nodeToNodeDTO(node); namesList.add(nodeDTO.getName()); } return namesList; } }