XMLTransferDAO.java
/*
* This file is part of Waarp Project (named also Waarp or GG).
*
* Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
* tags. See the COPYRIGHT.txt in the distribution for a full listing of
* individual contributors.
*
* All Waarp Project is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Waarp 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Waarp . If not, see <http://www.gnu.org/licenses/>.
*/
package org.waarp.openr66.dao.xml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.waarp.common.file.FileUtils;
import org.waarp.common.guid.LongUuid;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.lru.SynchronizedLruCache;
import org.waarp.openr66.context.ErrorCode;
import org.waarp.openr66.dao.Filter;
import org.waarp.openr66.dao.TransferDAO;
import org.waarp.openr66.dao.exception.DAOConnectionException;
import org.waarp.openr66.dao.exception.DAONoDataException;
import org.waarp.openr66.database.DbConstantR66;
import org.waarp.openr66.pojo.Transfer;
import org.waarp.openr66.protocol.configuration.Configuration;
import org.xml.sax.SAXException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import static org.waarp.openr66.dao.DAOFactory.*;
import static org.waarp.openr66.dao.database.DBTransferDAO.*;
public class XMLTransferDAO implements TransferDAO {
private static final WaarpLogger logger =
WaarpLoggerFactory.getLogger(XMLTransferDAO.class);
public static final String ROOT_LIST = "taskrunners";
public static final String ROOT_ELEMENT = "runner";
private static final String XML_SELECT =
"//runner[" + ID_FIELD + "='$" + ID_FIELD + "' and " + REQUESTER_FIELD +
"='$" + REQUESTER_FIELD + "' and " + REQUESTED_FIELD + "='$" +
REQUESTED_FIELD + "' and " + OWNER_REQUEST_FIELD + "='$" +
OWNER_REQUEST_FIELD + "']";
private static final String XML_GET_ALL = "runner";
/**
* HashTable in case of lack of database using LRU mode with 20 000 items
* maximum (< 200 MB?) for 180s
*/
private static SynchronizedLruCache<Long, Transfer> dbR66TaskHashMap;
private static boolean noFile = false;
/**
* @param newNoFile if True, no file will be created but only on Memory
* using LruCache
*/
public static void setNoFile(final boolean newNoFile) {
noFile = newNoFile;
}
/**
* Create the LRU cache
*
* @param limit limit of number of entries in the cache
* @param ttl time to leave used
*/
public static void createLruCache(final int limit, final long ttl) {
dbR66TaskHashMap = new SynchronizedLruCache<Long, Transfer>(limit, ttl);
}
public static String hashStatus() {
return "DbTaskRunner: [dbR66TaskHashMap: " + dbR66TaskHashMap.size() + "] ";
}
/**
* To enable clear of oldest entries in the cache
*
* @return the number of elements removed
*/
public static int clearCache() {
return dbR66TaskHashMap.forceClearOldest();
}
/**
* To update the TTL for the cache (to 10xTIMEOUT)
*
* @param ttl
*/
public static void updateLruCacheTimeout(final long ttl) {
dbR66TaskHashMap.setNewTtl(ttl);
}
/**
* To allow to remove specifically one SpecialId from MemoryHashmap
*
* @param specialId
*/
public static void removeNoDbSpecialId(final long specialId) {
dbR66TaskHashMap.remove(specialId);
}
/**
* To update the usage TTL of the associated object
*
* @param specialId
*/
public static void updateUsed(final long specialId) {
dbR66TaskHashMap.updateTtl(specialId);
}
public XMLTransferDAO() {
// Empty
}
@Override
public void close() {
// ignore
}
public static final String XMLEXTENSION = "_singlerunner.xml";
private File getFile(final String requester, final String requested,
final long id) {
return new File(Configuration.configuration.getBaseDirectory() +
Configuration.configuration.getArchivePath() + '/' +
requester + '_' + requested + '_' + id + XMLEXTENSION);
}
@Override
public void delete(final Transfer transfer) {
removeNoDbSpecialId(transfer.getId());
}
/**
* {@link DAOConnectionException}
*/
@Override
public void deleteAll() {
dbR66TaskHashMap.clear();
final File arch = new File(Configuration.configuration.getBaseDirectory() +
Configuration.configuration.getArchivePath());
if (arch.isDirectory()) {
FileUtils.forceDeleteRecursiveDir(arch);
}
}
@Override
public List<Transfer> getAll() throws DAOConnectionException {
if (noFile) {
return new ArrayList<Transfer>(dbR66TaskHashMap.values());
}
final File arch = new File(Configuration.configuration.getArchivePath());
final File[] runnerFiles = arch.listFiles(new FilenameFilter() {
@Override
public boolean accept(final File file, final String s) {
return s.endsWith(XMLEXTENSION);
}
});
final List<Transfer> res = new ArrayList<Transfer>();
if (runnerFiles != null) {
for (final File fileNew : runnerFiles) {
try {
final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
final Document document = dbf.newDocumentBuilder().parse(fileNew);
// Setup XPath query
final XPath xPath = XPathFactory.newInstance().newXPath();
final XPathExpression xpe = xPath.compile(XML_GET_ALL);
final NodeList listNode =
(NodeList) xpe.evaluate(document, XPathConstants.NODESET);
// Iterate through all found nodes
for (int i = 0; i < listNode.getLength(); i++) {
final Node node = listNode.item(i);
final Transfer transfer = getFromNode(node);
res.add(transfer);
dbR66TaskHashMap.put(transfer.getId(), transfer);
}
} catch (final SAXException e) {
throw new DAOConnectionException(e);
} catch (final XPathExpressionException e) {
throw new DAOConnectionException(e);
} catch (final ParserConfigurationException e) {
throw new DAOConnectionException(e);
} catch (final IOException e) {
throw new DAOConnectionException(e);
}
}
}
synchronized (dbR66TaskHashMap) {
for (final Transfer transfer : dbR66TaskHashMap.values()) {
if (transfer != null && !res.contains(transfer)) {
res.add(transfer);
}
}
}
return res;
}
@Override
public boolean exist(final long id, final String requester,
final String requested, final String owner) {
if (dbR66TaskHashMap.contains(id)) {
return true;
}
final File file = getFile(requester, requested, id);
return file.exists();
}
/**
* {@link DAOConnectionException}
*
* @return never
*/
@Override
public List<Transfer> find(final List<Filter> fitlers)
throws DAOConnectionException {
throw new DAOConnectionException("Operation not supported on XML DAO");
}
/**
* {@link DAOConnectionException}
*
* @return count only if filters is empty or null
*/
@Override
public long count(final List<Filter> fitlers) throws DAOConnectionException {
if (fitlers == null || fitlers.isEmpty()) {
return dbR66TaskHashMap.size();
}
throw new DAOConnectionException("Operation not supported on XML DAO");
}
/**
* {@link UnsupportedOperationException}
*
* @return never
*/
@Override
public Transfer select(final String id) {
throw new UnsupportedOperationException();
}
/**
* {@link UnsupportedOperationException}
*
* @return never
*/
@Override
public boolean exist(final String id) {
throw new UnsupportedOperationException();
}
/**
* {@link DAOConnectionException}
*
* @return never
*/
@Override
public List<Transfer> find(final List<Filter> filters, final int limit)
throws DAOConnectionException {
throw new DAOConnectionException("Operation not supported on XML DAO");
}
/**
* {@link DAOConnectionException}
*
* @return never
*/
@Override
public List<Transfer> find(final List<Filter> filters, final int limit,
final int offset) throws DAOConnectionException {
throw new DAOConnectionException("Operation not supported on XML DAO");
}
/**
* {@link DAOConnectionException}
*
* @return never
*/
@Override
public List<Transfer> find(final List<Filter> filters, final String column,
final boolean ascend)
throws DAOConnectionException {
throw new DAOConnectionException("Operation not supported on XML DAO");
}
/**
* {@link DAOConnectionException}
*
* @return never
*/
@Override
public List<Transfer> find(final List<Filter> filters, final String column,
final boolean ascend, final int limit)
throws DAOConnectionException {
throw new DAOConnectionException("Operation not supported on XML DAO");
}
/**
* {@link DAOConnectionException}
*
* @return never
*/
@Override
public List<Transfer> find(final List<Filter> filters, final String column,
final boolean ascend, final int limit,
final int offset) throws DAOConnectionException {
throw new DAOConnectionException("Operation not supported on XML DAO");
}
@Override
public void update(final List<Filter> filters, final String toSet)
throws DAOConnectionException {
throw new DAOConnectionException("Operation not supported on XML DAO");
}
@Override
public void insert(final Transfer transfer) throws DAOConnectionException {
// Set unique Id
if (transfer.getId() == DbConstantR66.ILLEGALVALUE) {
transfer.setId(LongUuid.getLongUuid());
}
dbR66TaskHashMap.put(transfer.getId(), transfer);
if (noFile) {
return;
}
final File file = getFile(transfer.getRequester(), transfer.getRequested(),
transfer.getId());
if (file.exists()) {
throw new DAOConnectionException(
"File already exist: " + file.getAbsolutePath());
}
try {
final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
final Document document = dbf.newDocumentBuilder().newDocument();
final Element root = document.createElement(ROOT_LIST);
document.appendChild(root);
root.appendChild(getNode(document, transfer));
// Write document in file
XMLUtils.writeToFile(file, document);
} catch (final ParserConfigurationException e) {
throw new DAOConnectionException(e);
}
}
@Override
public Transfer select(final long id, final String requester,
final String requested, final String owner)
throws DAOConnectionException, DAONoDataException {
if (dbR66TaskHashMap.contains(id)) {
final Transfer value = dbR66TaskHashMap.get(id);
if (value != null) {
dbR66TaskHashMap.updateTtl(id);
}
return value;
}
if (noFile) {
throw new DAONoDataException("Transfer cannot be found");
}
final File file = getFile(requester, requested, id);
if (!file.exists()) {
throw new DAONoDataException("Transfer cannot be found");
}
try {
final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
final Document document = dbf.newDocumentBuilder().parse(file);
// Setup XPath variable
final SimpleVariableResolver resolver = new SimpleVariableResolver();
resolver.addVariable(new QName(null, ID_FIELD), id);
resolver.addVariable(new QName(null, REQUESTER_FIELD), requester);
resolver.addVariable(new QName(null, REQUESTED_FIELD), requested);
resolver.addVariable(new QName(null, "owner"), owner);
// Setup XPath query
final XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setXPathVariableResolver(resolver);
final XPathExpression xpe = xPath.compile(XML_SELECT);
// Retrieve node and instantiate object
final Node node = (Node) xpe.evaluate(document, XPathConstants.NODE);
if (node != null) {
return getFromNode(node);
}
throw new DAONoDataException("Transfer cannot be found");
} catch (final SAXException e) {
throw new DAOConnectionException(e);
} catch (final XPathExpressionException e) {
throw new DAOConnectionException(e);
} catch (final ParserConfigurationException e) {
throw new DAOConnectionException(e);
} catch (final IOException e) {
throw new DAOConnectionException(e);
}
}
@Override
public void update(final Transfer transfer) throws DAOConnectionException {
dbR66TaskHashMap.put(transfer.getId(), transfer);
if (noFile) {
return;
}
final File file = getFile(transfer.getRequester(), transfer.getRequested(),
transfer.getId());
if (!file.exists()) {
throw new DAOConnectionException("File doesn't exist");
}
try {
final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
final Document document = dbf.newDocumentBuilder().parse(file);
final Element root = document.getDocumentElement();
Node node = null;
if (root.hasChildNodes()) {
final NodeList nodeList = root.getChildNodes();
final int nb = nodeList.getLength();
for (int i = 0; i < nb; i++) {
int found = -4;
node = nodeList.item(0);
if (node.hasChildNodes()) {
final NodeList nodeChildList = node.getChildNodes();
final int nbChild = nodeChildList.getLength();
for (int j = 0; j < nbChild; j++) {
final Node child = nodeChildList.item(j);
if (child.getNodeName().equals(ID_FIELD) && child.getTextContent()
.equals(
Long.toString(
transfer.getId()))) {
found++;
} else if (child.getNodeName().equals(OWNER_REQUEST_FIELD) &&
child.getTextContent()
.equals(transfer.getOwnerRequest())) {
found++;
} else if (child.getNodeName().equals(REQUESTER_FIELD) &&
child.getTextContent()
.equals(transfer.getRequester())) {
found++;
} else if (child.getNodeName().equals(REQUESTED_FIELD) &&
child.getTextContent()
.equals(transfer.getRequested())) {
found++;
}
if (found == 0) {
break;
}
}
if (found == 0) {
break;
}
}
}
}
if (node == null) {
logger.warn("Entry not found cannot update for {} {} {} {}",
transfer.getId(), transfer.getRequester(),
transfer.getRequested(), transfer.getOwnerRequest());
return;
}
node.getParentNode().removeChild(node);
// Insert updated node
root.appendChild(getNode(document, transfer));
// Write document in file
XMLUtils.writeToFile(file, document);
} catch (final SAXException e) {
throw new DAOConnectionException(e);
} catch (final ParserConfigurationException e) {
throw new DAOConnectionException(e);
} catch (final IOException e) {
throw new DAOConnectionException(e);
}
}
private Transfer getFromNode(final Node parent) {
final Transfer res = new Transfer();
final NodeList children = parent.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
final Node node = children.item(j);
if (node.getNodeName().equals(ID_FIELD)) {
res.setId(Long.parseLong(node.getTextContent()));
} else if (node.getNodeName().equals(OWNER_REQUEST_FIELD)) {
res.setOwnerRequest(node.getTextContent());
} else if (node.getNodeName().equals(REQUESTER_FIELD)) {
res.setRequester(node.getTextContent());
} else if (node.getNodeName().equals(REQUESTED_FIELD)) {
res.setRequested(node.getTextContent());
} else if (node.getNodeName().equals(ID_RULE_FIELD)) {
res.setRule(node.getTextContent());
} else if (node.getNodeName().equals(RETRIEVE_MODE_FIELD)) {
res.setRetrieveMode(Boolean.parseBoolean(node.getTextContent()));
} else if (node.getNodeName().equals(TRANSFER_MODE_FIELD)) {
res.setTransferMode(Integer.parseInt(node.getTextContent()));
} else if (node.getNodeName().equals(FILENAME_FIELD)) {
res.setFilename(node.getTextContent());
} else if (node.getNodeName().equals(ORIGINAL_NAME_FIELD)) {
res.setOriginalName(node.getTextContent());
} else if (node.getNodeName().equals(FILE_INFO_FIELD)) {
res.setFileInfo(node.getTextContent());
} else if (node.getNodeName().equals(TRANSFER_INFO_FIELD)) {
res.setTransferInfo(node.getTextContent());
} else if (node.getNodeName().equals(IS_MOVED_FIELD)) {
res.setIsMoved(Boolean.parseBoolean(node.getTextContent()));
} else if (node.getNodeName().equals(BLOCK_SIZE_FIELD)) {
res.setBlockSize(Integer.parseInt(node.getTextContent()));
} else if (node.getNodeName().equals(GLOBAL_STEP_FIELD)) {
res.setGlobalStep(
Transfer.TASKSTEP.valueOf(Integer.parseInt(node.getTextContent())));
} else if (node.getNodeName().equals(GLOBAL_LAST_STEP_FIELD)) {
res.setLastGlobalStep(
Transfer.TASKSTEP.valueOf(Integer.parseInt(node.getTextContent())));
} else if (node.getNodeName().equals(STEP_FIELD)) {
res.setStep(Integer.parseInt(node.getTextContent()));
} else if (node.getNodeName().equals(RANK_FIELD)) {
res.setRank(Integer.parseInt(node.getTextContent()));
} else if (node.getNodeName().equals(STEP_STATUS_FIELD)) {
res.setStepStatus(ErrorCode.getFromCode(node.getTextContent()));
} else if (node.getNodeName().equals(INFO_STATUS_FIELD)) {
res.setInfoStatus(ErrorCode.getFromCode(node.getTextContent()));
} else if (node.getNodeName().equals(TRANSFER_START_FIELD)) {
res.setStart(Timestamp.valueOf(node.getTextContent()));
} else if (node.getNodeName().equals(TRANSFER_STOP_FIELD)) {
res.setStop(Timestamp.valueOf(node.getTextContent()));
}
}
return res;
}
private Node getNode(final Document doc, final Transfer transfer) {
final Node res = doc.createElement(ROOT_ELEMENT);
res.appendChild(
XMLUtils.createNode(doc, ID_FIELD, Long.toString(transfer.getId())));
res.appendChild(XMLUtils.createNode(doc, OWNER_REQUEST_FIELD,
transfer.getOwnerRequest()));
res.appendChild(
XMLUtils.createNode(doc, REQUESTER_FIELD, transfer.getRequester()));
res.appendChild(
XMLUtils.createNode(doc, REQUESTED_FIELD, transfer.getRequested()));
res.appendChild(
XMLUtils.createNode(doc, ID_RULE_FIELD, transfer.getRule()));
res.appendChild(XMLUtils.createNode(doc, RETRIEVE_MODE_FIELD,
Boolean.toString(
transfer.getRetrieveMode())));
res.appendChild(XMLUtils.createNode(doc, TRANSFER_MODE_FIELD,
Integer.toString(
transfer.getTransferMode())));
res.appendChild(
XMLUtils.createNode(doc, FILENAME_FIELD, transfer.getRequested()));
res.appendChild(
XMLUtils.createNode(doc, ORIGINAL_NAME_FIELD, transfer.getFilename()));
res.appendChild(
XMLUtils.createNode(doc, REQUESTED_FIELD, transfer.getOriginalName()));
res.appendChild(
XMLUtils.createNode(doc, FILE_INFO_FIELD, transfer.getFileInfo()));
res.appendChild(XMLUtils.createNode(doc, TRANSFER_INFO_FIELD,
transfer.getTransferInfo()));
res.appendChild(XMLUtils.createNode(doc, IS_MOVED_FIELD, Boolean.toString(
transfer.getIsMoved())));
res.appendChild(XMLUtils.createNode(doc, BLOCK_SIZE_FIELD, Integer.toString(
transfer.getBlockSize())));
res.appendChild(XMLUtils.createNode(doc, GLOBAL_STEP_FIELD,
Integer.toString(
transfer.getGlobalStep()
.ordinal())));
res.appendChild(XMLUtils.createNode(doc, GLOBAL_LAST_STEP_FIELD,
Integer.toString(
transfer.getLastGlobalStep()
.ordinal())));
res.appendChild(XMLUtils.createNode(doc, STEP_FIELD,
Integer.toString(transfer.getStep())));
res.appendChild(XMLUtils.createNode(doc, RANK_FIELD,
Integer.toString(transfer.getRank())));
res.appendChild(XMLUtils.createNode(doc, STEP_STATUS_FIELD,
transfer.getStepStatus().getCode()));
res.appendChild(XMLUtils.createNode(doc, INFO_STATUS_FIELD,
transfer.getInfoStatus().getCode()));
res.appendChild(XMLUtils.createNode(doc, TRANSFER_START_FIELD,
transfer.getStart().toString()));
res.appendChild(XMLUtils.createNode(doc, TRANSFER_STOP_FIELD,
transfer.getStop().toString()));
return res;
}
}