View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  
21  package org.waarp.openr66.dao.xml;
22  
23  import org.w3c.dom.Document;
24  import org.w3c.dom.Element;
25  import org.w3c.dom.Node;
26  import org.w3c.dom.NodeList;
27  import org.waarp.common.file.FileUtils;
28  import org.waarp.common.guid.LongUuid;
29  import org.waarp.common.logging.WaarpLogger;
30  import org.waarp.common.logging.WaarpLoggerFactory;
31  import org.waarp.common.lru.SynchronizedLruCache;
32  import org.waarp.openr66.context.ErrorCode;
33  import org.waarp.openr66.dao.Filter;
34  import org.waarp.openr66.dao.TransferDAO;
35  import org.waarp.openr66.dao.exception.DAOConnectionException;
36  import org.waarp.openr66.dao.exception.DAONoDataException;
37  import org.waarp.openr66.database.DbConstantR66;
38  import org.waarp.openr66.pojo.Transfer;
39  import org.waarp.openr66.protocol.configuration.Configuration;
40  import org.xml.sax.SAXException;
41  
42  import javax.xml.namespace.QName;
43  import javax.xml.parsers.DocumentBuilderFactory;
44  import javax.xml.parsers.ParserConfigurationException;
45  import javax.xml.xpath.XPath;
46  import javax.xml.xpath.XPathConstants;
47  import javax.xml.xpath.XPathExpression;
48  import javax.xml.xpath.XPathExpressionException;
49  import javax.xml.xpath.XPathFactory;
50  import java.io.File;
51  import java.io.FilenameFilter;
52  import java.io.IOException;
53  import java.sql.Timestamp;
54  import java.util.ArrayList;
55  import java.util.List;
56  
57  import static org.waarp.openr66.dao.DAOFactory.*;
58  import static org.waarp.openr66.dao.database.DBTransferDAO.*;
59  
60  public class XMLTransferDAO implements TransferDAO {
61  
62    private static final WaarpLogger logger =
63        WaarpLoggerFactory.getLogger(XMLTransferDAO.class);
64  
65    public static final String ROOT_LIST = "taskrunners";
66    public static final String ROOT_ELEMENT = "runner";
67  
68  
69    private static final String XML_SELECT =
70        "//runner[" + ID_FIELD + "='$" + ID_FIELD + "' and " + REQUESTER_FIELD +
71        "='$" + REQUESTER_FIELD + "' and " + REQUESTED_FIELD + "='$" +
72        REQUESTED_FIELD + "' and " + OWNER_REQUEST_FIELD + "='$" +
73        OWNER_REQUEST_FIELD + "']";
74    private static final String XML_GET_ALL = "runner";
75  
76    /**
77     * HashTable in case of lack of database using LRU mode with 20 000 items
78     * maximum (< 200 MB?) for 180s
79     */
80    private static SynchronizedLruCache<Long, Transfer> dbR66TaskHashMap;
81  
82    private static boolean noFile = false;
83  
84    /**
85     * @param newNoFile if True, no file will be created but only on Memory
86     *     using LruCache
87     */
88    public static void setNoFile(final boolean newNoFile) {
89      noFile = newNoFile;
90    }
91  
92    /**
93     * Create the LRU cache
94     *
95     * @param limit limit of number of entries in the cache
96     * @param ttl time to leave used
97     */
98    public static void createLruCache(final int limit, final long ttl) {
99      dbR66TaskHashMap = new SynchronizedLruCache<Long, Transfer>(limit, ttl);
100   }
101 
102   public static String hashStatus() {
103     return "DbTaskRunner: [dbR66TaskHashMap: " + dbR66TaskHashMap.size() + "] ";
104   }
105 
106   /**
107    * To enable clear of oldest entries in the cache
108    *
109    * @return the number of elements removed
110    */
111   public static int clearCache() {
112     return dbR66TaskHashMap.forceClearOldest();
113   }
114 
115   /**
116    * To update the TTL for the cache (to 10xTIMEOUT)
117    *
118    * @param ttl
119    */
120   public static void updateLruCacheTimeout(final long ttl) {
121     dbR66TaskHashMap.setNewTtl(ttl);
122   }
123 
124   /**
125    * To allow to remove specifically one SpecialId from MemoryHashmap
126    *
127    * @param specialId
128    */
129   public static void removeNoDbSpecialId(final long specialId) {
130     dbR66TaskHashMap.remove(specialId);
131   }
132 
133   /**
134    * To update the usage TTL of the associated object
135    *
136    * @param specialId
137    */
138   public static void updateUsed(final long specialId) {
139     dbR66TaskHashMap.updateTtl(specialId);
140   }
141 
142   public XMLTransferDAO() {
143     // Empty
144   }
145 
146   @Override
147   public void close() {
148     // ignore
149   }
150 
151   public static final String XMLEXTENSION = "_singlerunner.xml";
152 
153   private File getFile(final String requester, final String requested,
154                        final long id) {
155     return new File(Configuration.configuration.getBaseDirectory() +
156                     Configuration.configuration.getArchivePath() + '/' +
157                     requester + '_' + requested + '_' + id + XMLEXTENSION);
158   }
159 
160   @Override
161   public void delete(final Transfer transfer) {
162     removeNoDbSpecialId(transfer.getId());
163   }
164 
165   /**
166    * {@link DAOConnectionException}
167    */
168   @Override
169   public void deleteAll() {
170     dbR66TaskHashMap.clear();
171     final File arch = new File(Configuration.configuration.getBaseDirectory() +
172                                Configuration.configuration.getArchivePath());
173     if (arch.isDirectory()) {
174       FileUtils.forceDeleteRecursiveDir(arch);
175     }
176   }
177 
178   @Override
179   public List<Transfer> getAll() throws DAOConnectionException {
180     if (noFile) {
181       return new ArrayList<Transfer>(dbR66TaskHashMap.values());
182     }
183     final File arch = new File(Configuration.configuration.getArchivePath());
184     final File[] runnerFiles = arch.listFiles(new FilenameFilter() {
185       @Override
186       public boolean accept(final File file, final String s) {
187         return s.endsWith(XMLEXTENSION);
188       }
189     });
190     final List<Transfer> res = new ArrayList<Transfer>();
191     if (runnerFiles != null) {
192       for (final File fileNew : runnerFiles) {
193         try {
194           final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
195           final Document document = dbf.newDocumentBuilder().parse(fileNew);
196           // Setup XPath query
197           final XPath xPath = XPathFactory.newInstance().newXPath();
198           final XPathExpression xpe = xPath.compile(XML_GET_ALL);
199           final NodeList listNode =
200               (NodeList) xpe.evaluate(document, XPathConstants.NODESET);
201           // Iterate through all found nodes
202           for (int i = 0; i < listNode.getLength(); i++) {
203             final Node node = listNode.item(i);
204             final Transfer transfer = getFromNode(node);
205             res.add(transfer);
206             dbR66TaskHashMap.put(transfer.getId(), transfer);
207           }
208         } catch (final SAXException e) {
209           throw new DAOConnectionException(e);
210         } catch (final XPathExpressionException e) {
211           throw new DAOConnectionException(e);
212         } catch (final ParserConfigurationException e) {
213           throw new DAOConnectionException(e);
214         } catch (final IOException e) {
215           throw new DAOConnectionException(e);
216         }
217       }
218     }
219     synchronized (dbR66TaskHashMap) {
220       for (final Transfer transfer : dbR66TaskHashMap.values()) {
221         if (transfer != null && !res.contains(transfer)) {
222           res.add(transfer);
223         }
224       }
225     }
226     return res;
227   }
228 
229   @Override
230   public boolean exist(final long id, final String requester,
231                        final String requested, final String owner) {
232     if (dbR66TaskHashMap.contains(id)) {
233       return true;
234     }
235     final File file = getFile(requester, requested, id);
236     return file.exists();
237   }
238 
239   /**
240    * {@link DAOConnectionException}
241    *
242    * @return never
243    */
244   @Override
245   public List<Transfer> find(final List<Filter> fitlers)
246       throws DAOConnectionException {
247     throw new DAOConnectionException("Operation not supported on XML DAO");
248   }
249 
250   /**
251    * {@link DAOConnectionException}
252    *
253    * @return count only if filters is empty or null
254    */
255   @Override
256   public long count(final List<Filter> fitlers) throws DAOConnectionException {
257     if (fitlers == null || fitlers.isEmpty()) {
258       return dbR66TaskHashMap.size();
259     }
260     throw new DAOConnectionException("Operation not supported on XML DAO");
261   }
262 
263   /**
264    * {@link UnsupportedOperationException}
265    *
266    * @return never
267    */
268   @Override
269   public Transfer select(final String id) {
270     throw new UnsupportedOperationException();
271   }
272 
273   /**
274    * {@link UnsupportedOperationException}
275    *
276    * @return never
277    */
278   @Override
279   public boolean exist(final String id) {
280     throw new UnsupportedOperationException();
281   }
282 
283   /**
284    * {@link DAOConnectionException}
285    *
286    * @return never
287    */
288   @Override
289   public List<Transfer> find(final List<Filter> filters, final int limit)
290       throws DAOConnectionException {
291     throw new DAOConnectionException("Operation not supported on XML DAO");
292   }
293 
294   /**
295    * {@link DAOConnectionException}
296    *
297    * @return never
298    */
299   @Override
300   public List<Transfer> find(final List<Filter> filters, final int limit,
301                              final int offset) throws DAOConnectionException {
302     throw new DAOConnectionException("Operation not supported on XML DAO");
303   }
304 
305   /**
306    * {@link DAOConnectionException}
307    *
308    * @return never
309    */
310   @Override
311   public List<Transfer> find(final List<Filter> filters, final String column,
312                              final boolean ascend)
313       throws DAOConnectionException {
314     throw new DAOConnectionException("Operation not supported on XML DAO");
315   }
316 
317   /**
318    * {@link DAOConnectionException}
319    *
320    * @return never
321    */
322   @Override
323   public List<Transfer> find(final List<Filter> filters, final String column,
324                              final boolean ascend, final int limit)
325       throws DAOConnectionException {
326     throw new DAOConnectionException("Operation not supported on XML DAO");
327   }
328 
329   /**
330    * {@link DAOConnectionException}
331    *
332    * @return never
333    */
334   @Override
335   public List<Transfer> find(final List<Filter> filters, final String column,
336                              final boolean ascend, final int limit,
337                              final int offset) throws DAOConnectionException {
338     throw new DAOConnectionException("Operation not supported on XML DAO");
339   }
340 
341   @Override
342   public void update(final List<Filter> filters, final String toSet)
343       throws DAOConnectionException {
344     throw new DAOConnectionException("Operation not supported on XML DAO");
345   }
346 
347   @Override
348   public void insert(final Transfer transfer) throws DAOConnectionException {
349     // Set unique Id
350     if (transfer.getId() == DbConstantR66.ILLEGALVALUE) {
351       transfer.setId(LongUuid.getLongUuid());
352     }
353     dbR66TaskHashMap.put(transfer.getId(), transfer);
354     if (noFile) {
355       return;
356     }
357     final File file = getFile(transfer.getRequester(), transfer.getRequested(),
358                               transfer.getId());
359     if (file.exists()) {
360       throw new DAOConnectionException(
361           "File already exist: " + file.getAbsolutePath());
362     }
363     try {
364       final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
365       final Document document = dbf.newDocumentBuilder().newDocument();
366       final Element root = document.createElement(ROOT_LIST);
367       document.appendChild(root);
368       root.appendChild(getNode(document, transfer));
369       // Write document in file
370       XMLUtils.writeToFile(file, document);
371     } catch (final ParserConfigurationException e) {
372       throw new DAOConnectionException(e);
373     }
374   }
375 
376   @Override
377   public Transfer select(final long id, final String requester,
378                          final String requested, final String owner)
379       throws DAOConnectionException, DAONoDataException {
380     if (dbR66TaskHashMap.contains(id)) {
381       final Transfer value = dbR66TaskHashMap.get(id);
382       if (value != null) {
383         dbR66TaskHashMap.updateTtl(id);
384       }
385       return value;
386     }
387     if (noFile) {
388       throw new DAONoDataException("Transfer cannot be found");
389     }
390     final File file = getFile(requester, requested, id);
391     if (!file.exists()) {
392       throw new DAONoDataException("Transfer cannot be found");
393     }
394     try {
395       final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
396       final Document document = dbf.newDocumentBuilder().parse(file);
397       // Setup XPath variable
398       final SimpleVariableResolver resolver = new SimpleVariableResolver();
399       resolver.addVariable(new QName(null, ID_FIELD), id);
400       resolver.addVariable(new QName(null, REQUESTER_FIELD), requester);
401       resolver.addVariable(new QName(null, REQUESTED_FIELD), requested);
402       resolver.addVariable(new QName(null, "owner"), owner);
403       // Setup XPath query
404       final XPath xPath = XPathFactory.newInstance().newXPath();
405       xPath.setXPathVariableResolver(resolver);
406       final XPathExpression xpe = xPath.compile(XML_SELECT);
407       // Retrieve node and instantiate object
408       final Node node = (Node) xpe.evaluate(document, XPathConstants.NODE);
409       if (node != null) {
410         return getFromNode(node);
411       }
412       throw new DAONoDataException("Transfer cannot be found");
413     } catch (final SAXException e) {
414       throw new DAOConnectionException(e);
415     } catch (final XPathExpressionException e) {
416       throw new DAOConnectionException(e);
417     } catch (final ParserConfigurationException e) {
418       throw new DAOConnectionException(e);
419     } catch (final IOException e) {
420       throw new DAOConnectionException(e);
421     }
422   }
423 
424   @Override
425   public void update(final Transfer transfer) throws DAOConnectionException {
426     dbR66TaskHashMap.put(transfer.getId(), transfer);
427     if (noFile) {
428       return;
429     }
430     final File file = getFile(transfer.getRequester(), transfer.getRequested(),
431                               transfer.getId());
432     if (!file.exists()) {
433       throw new DAOConnectionException("File doesn't exist");
434     }
435     try {
436       final DocumentBuilderFactory dbf = getDocumentBuilderFactory();
437       final Document document = dbf.newDocumentBuilder().parse(file);
438       final Element root = document.getDocumentElement();
439       Node node = null;
440       if (root.hasChildNodes()) {
441         final NodeList nodeList = root.getChildNodes();
442         final int nb = nodeList.getLength();
443         for (int i = 0; i < nb; i++) {
444           int found = -4;
445           node = nodeList.item(0);
446           if (node.hasChildNodes()) {
447             final NodeList nodeChildList = node.getChildNodes();
448             final int nbChild = nodeChildList.getLength();
449             for (int j = 0; j < nbChild; j++) {
450               final Node child = nodeChildList.item(j);
451               if (child.getNodeName().equals(ID_FIELD) && child.getTextContent()
452                                                                .equals(
453                                                                    Long.toString(
454                                                                        transfer.getId()))) {
455                 found++;
456               } else if (child.getNodeName().equals(OWNER_REQUEST_FIELD) &&
457                          child.getTextContent()
458                               .equals(transfer.getOwnerRequest())) {
459                 found++;
460               } else if (child.getNodeName().equals(REQUESTER_FIELD) &&
461                          child.getTextContent()
462                               .equals(transfer.getRequester())) {
463                 found++;
464               } else if (child.getNodeName().equals(REQUESTED_FIELD) &&
465                          child.getTextContent()
466                               .equals(transfer.getRequested())) {
467                 found++;
468               }
469               if (found == 0) {
470                 break;
471               }
472             }
473             if (found == 0) {
474               break;
475             }
476           }
477         }
478       }
479       if (node == null) {
480         logger.warn("Entry not found cannot update for {} {} {} {}",
481                     transfer.getId(), transfer.getRequester(),
482                     transfer.getRequested(), transfer.getOwnerRequest());
483         return;
484       }
485       node.getParentNode().removeChild(node);
486       // Insert updated node
487       root.appendChild(getNode(document, transfer));
488       // Write document in file
489       XMLUtils.writeToFile(file, document);
490     } catch (final SAXException e) {
491       throw new DAOConnectionException(e);
492     } catch (final ParserConfigurationException e) {
493       throw new DAOConnectionException(e);
494     } catch (final IOException e) {
495       throw new DAOConnectionException(e);
496     }
497   }
498 
499   private Transfer getFromNode(final Node parent) {
500     final Transfer res = new Transfer();
501     final NodeList children = parent.getChildNodes();
502     for (int j = 0; j < children.getLength(); j++) {
503       final Node node = children.item(j);
504       if (node.getNodeName().equals(ID_FIELD)) {
505         res.setId(Long.parseLong(node.getTextContent()));
506       } else if (node.getNodeName().equals(OWNER_REQUEST_FIELD)) {
507         res.setOwnerRequest(node.getTextContent());
508       } else if (node.getNodeName().equals(REQUESTER_FIELD)) {
509         res.setRequester(node.getTextContent());
510       } else if (node.getNodeName().equals(REQUESTED_FIELD)) {
511         res.setRequested(node.getTextContent());
512       } else if (node.getNodeName().equals(ID_RULE_FIELD)) {
513         res.setRule(node.getTextContent());
514       } else if (node.getNodeName().equals(RETRIEVE_MODE_FIELD)) {
515         res.setRetrieveMode(Boolean.parseBoolean(node.getTextContent()));
516       } else if (node.getNodeName().equals(TRANSFER_MODE_FIELD)) {
517         res.setTransferMode(Integer.parseInt(node.getTextContent()));
518       } else if (node.getNodeName().equals(FILENAME_FIELD)) {
519         res.setFilename(node.getTextContent());
520       } else if (node.getNodeName().equals(ORIGINAL_NAME_FIELD)) {
521         res.setOriginalName(node.getTextContent());
522       } else if (node.getNodeName().equals(FILE_INFO_FIELD)) {
523         res.setFileInfo(node.getTextContent());
524       } else if (node.getNodeName().equals(TRANSFER_INFO_FIELD)) {
525         res.setTransferInfo(node.getTextContent());
526       } else if (node.getNodeName().equals(IS_MOVED_FIELD)) {
527         res.setIsMoved(Boolean.parseBoolean(node.getTextContent()));
528       } else if (node.getNodeName().equals(BLOCK_SIZE_FIELD)) {
529         res.setBlockSize(Integer.parseInt(node.getTextContent()));
530       } else if (node.getNodeName().equals(GLOBAL_STEP_FIELD)) {
531         res.setGlobalStep(
532             Transfer.TASKSTEP.valueOf(Integer.parseInt(node.getTextContent())));
533       } else if (node.getNodeName().equals(GLOBAL_LAST_STEP_FIELD)) {
534         res.setLastGlobalStep(
535             Transfer.TASKSTEP.valueOf(Integer.parseInt(node.getTextContent())));
536       } else if (node.getNodeName().equals(STEP_FIELD)) {
537         res.setStep(Integer.parseInt(node.getTextContent()));
538       } else if (node.getNodeName().equals(RANK_FIELD)) {
539         res.setRank(Integer.parseInt(node.getTextContent()));
540       } else if (node.getNodeName().equals(STEP_STATUS_FIELD)) {
541         res.setStepStatus(ErrorCode.getFromCode(node.getTextContent()));
542       } else if (node.getNodeName().equals(INFO_STATUS_FIELD)) {
543         res.setInfoStatus(ErrorCode.getFromCode(node.getTextContent()));
544       } else if (node.getNodeName().equals(TRANSFER_START_FIELD)) {
545         res.setStart(Timestamp.valueOf(node.getTextContent()));
546       } else if (node.getNodeName().equals(TRANSFER_STOP_FIELD)) {
547         res.setStop(Timestamp.valueOf(node.getTextContent()));
548       }
549     }
550     return res;
551   }
552 
553   private Node getNode(final Document doc, final Transfer transfer) {
554     final Node res = doc.createElement(ROOT_ELEMENT);
555     res.appendChild(
556         XMLUtils.createNode(doc, ID_FIELD, Long.toString(transfer.getId())));
557     res.appendChild(XMLUtils.createNode(doc, OWNER_REQUEST_FIELD,
558                                         transfer.getOwnerRequest()));
559     res.appendChild(
560         XMLUtils.createNode(doc, REQUESTER_FIELD, transfer.getRequester()));
561     res.appendChild(
562         XMLUtils.createNode(doc, REQUESTED_FIELD, transfer.getRequested()));
563     res.appendChild(
564         XMLUtils.createNode(doc, ID_RULE_FIELD, transfer.getRule()));
565     res.appendChild(XMLUtils.createNode(doc, RETRIEVE_MODE_FIELD,
566                                         Boolean.toString(
567                                             transfer.getRetrieveMode())));
568     res.appendChild(XMLUtils.createNode(doc, TRANSFER_MODE_FIELD,
569                                         Integer.toString(
570                                             transfer.getTransferMode())));
571     res.appendChild(
572         XMLUtils.createNode(doc, FILENAME_FIELD, transfer.getRequested()));
573     res.appendChild(
574         XMLUtils.createNode(doc, ORIGINAL_NAME_FIELD, transfer.getFilename()));
575     res.appendChild(
576         XMLUtils.createNode(doc, REQUESTED_FIELD, transfer.getOriginalName()));
577     res.appendChild(
578         XMLUtils.createNode(doc, FILE_INFO_FIELD, transfer.getFileInfo()));
579     res.appendChild(XMLUtils.createNode(doc, TRANSFER_INFO_FIELD,
580                                         transfer.getTransferInfo()));
581     res.appendChild(XMLUtils.createNode(doc, IS_MOVED_FIELD, Boolean.toString(
582         transfer.getIsMoved())));
583     res.appendChild(XMLUtils.createNode(doc, BLOCK_SIZE_FIELD, Integer.toString(
584         transfer.getBlockSize())));
585     res.appendChild(XMLUtils.createNode(doc, GLOBAL_STEP_FIELD,
586                                         Integer.toString(
587                                             transfer.getGlobalStep()
588                                                     .ordinal())));
589     res.appendChild(XMLUtils.createNode(doc, GLOBAL_LAST_STEP_FIELD,
590                                         Integer.toString(
591                                             transfer.getLastGlobalStep()
592                                                     .ordinal())));
593     res.appendChild(XMLUtils.createNode(doc, STEP_FIELD,
594                                         Integer.toString(transfer.getStep())));
595     res.appendChild(XMLUtils.createNode(doc, RANK_FIELD,
596                                         Integer.toString(transfer.getRank())));
597     res.appendChild(XMLUtils.createNode(doc, STEP_STATUS_FIELD,
598                                         transfer.getStepStatus().getCode()));
599     res.appendChild(XMLUtils.createNode(doc, INFO_STATUS_FIELD,
600                                         transfer.getInfoStatus().getCode()));
601     res.appendChild(XMLUtils.createNode(doc, TRANSFER_START_FIELD,
602                                         transfer.getStart().toString()));
603     res.appendChild(XMLUtils.createNode(doc, TRANSFER_STOP_FIELD,
604                                         transfer.getStop().toString()));
605     return res;
606   }
607 }