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.protocol.http.restv2.converters;
22  
23  import com.fasterxml.jackson.databind.JsonNode;
24  import com.fasterxml.jackson.databind.node.ObjectNode;
25  import org.joda.time.DateTime;
26  import org.waarp.common.database.exception.WaarpDatabaseException;
27  import org.waarp.common.database.exception.WaarpDatabaseSqlException;
28  import org.waarp.common.json.JsonHandler;
29  import org.waarp.common.logging.SysErrLogger;
30  import org.waarp.common.utility.ParametersChecker;
31  import org.waarp.openr66.dao.DAOFactory;
32  import org.waarp.openr66.dao.HostDAO;
33  import org.waarp.openr66.dao.RuleDAO;
34  import org.waarp.openr66.dao.exception.DAOConnectionException;
35  import org.waarp.openr66.dao.exception.DAONoDataException;
36  import org.waarp.openr66.pojo.Rule;
37  import org.waarp.openr66.pojo.Transfer;
38  import org.waarp.openr66.pojo.UpdatedInfo;
39  import org.waarp.openr66.protocol.configuration.Configuration;
40  import org.waarp.openr66.protocol.http.restv2.converters.RuleConverter.ModeTrans;
41  import org.waarp.openr66.protocol.http.restv2.errors.RestError;
42  import org.waarp.openr66.protocol.http.restv2.errors.RestErrorException;
43  
44  import javax.ws.rs.InternalServerErrorException;
45  import java.sql.Timestamp;
46  import java.util.ArrayList;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.Map;
50  
51  import static org.waarp.common.file.FileUtils.*;
52  import static org.waarp.openr66.dao.database.DBTransferDAO.*;
53  import static org.waarp.openr66.protocol.http.restv2.RestConstants.*;
54  import static org.waarp.openr66.protocol.http.restv2.RestConstants.TransferFields.*;
55  import static org.waarp.openr66.protocol.http.restv2.errors.RestErrors.*;
56  
57  /**
58   * A collection of utility methods to convert {@link Transfer} objects to their
59   * corresponding
60   * {@link ObjectNode} and vice-versa.
61   */
62  public final class TransferConverter {
63  
64    /**
65     * Makes the default constructor of this utility class inaccessible.
66     */
67    private TransferConverter() throws InstantiationException {
68      throw new InstantiationException(
69          getClass().getName() + " cannot be instantiated.");
70    }
71  
72    // ########################### INNER CLASSES ################################
73  
74    /**
75     * All the possible ways to order a list of transfer objects.
76     */
77    public enum Order {
78      /**
79       * By transferId, in ascending order.
80       */
81      ascId(ID_FIELD, true),
82      /**
83       * By transferId, in descending order.
84       */
85      descId(ID_FIELD, false),
86      /**
87       * By fileName, in ascending order.
88       */
89      ascFile(ORIGINAL_NAME_FIELD, true),
90      /**
91       * By fileName, in descending order.
92       */
93      descFile(ORIGINAL_NAME_FIELD, false),
94      /**
95       * By starting date, in ascending order.
96       */
97      ascStart(TRANSFER_START_FIELD, true),
98      /**
99       * By starting date, in descending order.
100      */
101     descStart(TRANSFER_START_FIELD, false),
102     /**
103      * By end date, in ascending order.
104      */
105     ascStop(TRANSFER_STOP_FIELD, true),
106     /**
107      * By end date, in descending order.
108      */
109     descStop(TRANSFER_STOP_FIELD, false);
110 
111     /**
112      * The name of the database column used for sorting.
113      */
114     public final String column;
115     /**
116      * If the order is ascending or descending.
117      */
118     public final boolean ascend;
119 
120     Order(final String column, final boolean ascend) {
121       this.column = column;
122       this.ascend = ascend;
123     }
124   }
125 
126   // ########################## PUBLIC METHODS ################################
127 
128   /**
129    * Returns an {@link ObjectNode} representing the {@link Transfer} object
130    * given as parameter.
131    *
132    * @param transfer the Transfer object to serialize
133    *
134    * @return the corresponding ObjectNode
135    */
136   public static ObjectNode transferToNode(final Transfer transfer) {
137     final ObjectNode node = JsonHandler.createObjectNode();
138     node.put(TRANSFER_ID, transfer.getId());
139     node.put(GLOBAL_STEP, transfer.getGlobalStep().toString());
140     node.put(GLOBAL_LAST_STEP, transfer.getLastGlobalStep().toString());
141     node.put(STEP, transfer.getStep());
142     node.put(RANK, transfer.getRank());
143     node.put(UPDATED_INFO, transfer.getUpdatedInfo().toString());
144     node.put(STEP_STATUS, transfer.getStepStatus().toString());
145     node.put(ERROR_CODE, transfer.getInfoStatus().code);
146     node.put(ERROR_MESSAGE, transfer.getInfoStatus().getMesg());
147     node.put(ORIGINAL_FILENAME, transfer.getOriginalName());
148     node.put(FILENAME, transfer.getFilename());
149     node.put(RULE, transfer.getRule());
150     node.put(BLOCK_SIZE, transfer.getBlockSize());
151     node.put(FILE_INFO, transfer.getFileInfo());
152     node.put(TRANSFER_INFO, transfer.getTransferInfo());
153     node.put(START, new DateTime(transfer.getStart()).toString());
154     node.put(STOP, new DateTime(transfer.getStop()).toString());
155     node.put(REQUESTED, transfer.getRequested());
156     node.put(REQUESTER, transfer.getRequester());
157     node.put(RETRIEVE, transfer.getRetrieveMode());
158 
159     return node;
160   }
161 
162   /**
163    * Initialize a {@link Transfer} object using the values of the given {@link
164    * ObjectNode}.
165    *
166    * @param object the ObjectNode to convert
167    *
168    * @return the new Transfer object
169    *
170    * @throws RestErrorException if the given ObjectNode does not
171    *     represent a Transfer object
172    * @throws InternalServerErrorException if an unexpected error
173    *     occurred
174    */
175   public static Transfer nodeToNewTransfer(final ObjectNode object) {
176     Transfer defaultTransfer = null;
177     try {
178       defaultTransfer =
179           new Transfer(null, null, -1, false, null, null, ZERO_COPY_CHUNK_SIZE);
180     } catch (final WaarpDatabaseSqlException e) {
181       SysErrLogger.FAKE_LOGGER.syserr(e);
182       throw new IllegalArgumentException(e);
183     }
184     defaultTransfer.setRequester(serverName());
185     defaultTransfer.setOwnerRequest(serverName());
186     defaultTransfer.setBlockSize(Configuration.configuration.getBlockSize());
187     defaultTransfer.setTransferInfo("{}");
188     defaultTransfer.setStart(new Timestamp(DateTime.now().getMillis()));
189     final Transfer transfer = parseNode(object, defaultTransfer);
190 
191     ModeTrans mode;
192     RuleDAO ruleDAO = null;
193     try {
194       ruleDAO = DAO_FACTORY.getRuleDAO(true);
195       final Rule rule = ruleDAO.select(transfer.getRule());
196       mode = ModeTrans.fromCode(rule.getMode());
197     } catch (final DAOConnectionException e) {
198       throw new InternalServerErrorException(e);
199     } catch (final DAONoDataException e) {
200       throw new InternalServerErrorException(e);
201     } finally {
202       DAOFactory.closeDAO(ruleDAO);
203     }
204 
205     transfer.setRetrieveMode(
206         mode == ModeTrans.receive || mode == ModeTrans.receiveMD5);
207     transfer.setTransferMode(mode.code);
208     transfer.setStop(transfer.getStart());
209     transfer.setUpdatedInfo(UpdatedInfo.TOSUBMIT);
210 
211     return transfer;
212   }
213 
214   // ######################### PRIVATE METHODS ################################
215 
216   /**
217    * Tells if the given rule exists in the database.
218    *
219    * @param rule the name of the rule
220    *
221    * @return {@code true} if the rule exists, {@code false} otherwise.
222    */
223   private static boolean ruleExists(final String rule) {
224     RuleDAO ruleDAO = null;
225     try {
226       ruleDAO = DAO_FACTORY.getRuleDAO(true);
227       return ruleDAO.exist(rule);
228     } catch (final DAOConnectionException e) {
229       throw new InternalServerErrorException(e);
230     } finally {
231       DAOFactory.closeDAO(ruleDAO);
232     }
233   }
234 
235   /**
236    * Tells if the given host exists in the database.
237    *
238    * @param host the name of the host
239    *
240    * @return {@code true} if the host exists, {@code false} otherwise.
241    */
242   private static boolean hostExists(final String host) {
243     HostDAO hostDAO = null;
244     try {
245       hostDAO = DAO_FACTORY.getHostDAO(true);
246       return hostDAO.exist(host);
247     } catch (final DAOConnectionException e) {
248       throw new InternalServerErrorException(e);
249     } finally {
250       DAOFactory.closeDAO(hostDAO);
251     }
252   }
253 
254   /**
255    * Tells if the given host is allowed to use given rule.
256    *
257    * @param host the name of the host
258    * @param rule the name of the rule
259    *
260    * @return {@code true} if the host is allowed to use the rule, {@code
261    *     false} otherwise
262    */
263   private static boolean canUseRule(final String host, final String rule) {
264     RuleDAO ruleDAO = null;
265     try {
266       ruleDAO = DAO_FACTORY.getRuleDAO(true);
267       final List<String> hostIds = ruleDAO.select(rule).getHostids();
268       return !hostIds.isEmpty() && !hostIds.contains(host);
269     } catch (final DAOConnectionException e) {
270       throw new InternalServerErrorException(e);
271     } catch (final DAONoDataException e) {
272       throw new InternalServerErrorException(e);
273     } finally {
274       DAOFactory.closeDAO(ruleDAO);
275     }
276   }
277 
278   /**
279    * Returns a list of {@link RestError} corresponding to all the fields
280    * required to initialize a transfer that
281    * are missing from the given {@link Transfer} object. If no fields are
282    * missing, an empty list is returned.
283    *
284    * @param transfer the Transfer object to check.
285    *
286    * @return the list of all missing fields
287    */
288   private static List<RestError> checkRequiredFields(final Transfer transfer) {
289     final List<RestError> errors = new ArrayList<RestError>();
290     if (ParametersChecker.isEmpty(transfer.getRule())) {
291       errors.add(MISSING_FIELD(RULE));
292     }
293     if (ParametersChecker.isEmpty(transfer.getOriginalName())) {
294       errors.add(MISSING_FIELD(FILENAME));
295     }
296     if (ParametersChecker.isEmpty(transfer.getRequested())) {
297       errors.add(MISSING_FIELD(REQUESTED));
298     }
299 
300     return errors;
301   }
302 
303   /**
304    * Fills the fields of the given {@link Transfer} object with the values
305    * extracted from the {@link ObjectNode}
306    * parameter, and returns the result.
307    *
308    * @param object the ObjectNode from which the values should be
309    *     extracted
310    * @param transfer the Transfer object whose fields will be filled
311    *
312    * @return the filled Transfer object
313    *
314    * @throws RestErrorException if the given ObjectNode does not
315    *     represent a Transfer object.
316    */
317   private static Transfer parseNode(final ObjectNode object,
318                                     final Transfer transfer) {
319     final List<RestError> errors = new ArrayList<RestError>();
320 
321     final Iterator<Map.Entry<String, JsonNode>> fields = object.fields();
322     while (fields.hasNext()) {
323       final Map.Entry<String, JsonNode> field = fields.next();
324       final String name = field.getKey();
325       final JsonNode value = field.getValue();
326 
327       if (name.equalsIgnoreCase(RULE)) {
328         if (value.isTextual()) {
329           if (ruleExists(value.asText())) {
330             transfer.setRule(value.asText());
331           } else {
332             errors.add(UNKNOWN_RULE(value.asText()));
333           }
334         } else {
335           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
336         }
337       } else if (name.equalsIgnoreCase(FILENAME)) {
338         if (value.isTextual()) {
339           transfer.setOriginalName(value.asText());
340           transfer.setFilename(value.asText());
341         } else {
342           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
343         }
344       } else if (name.equalsIgnoreCase(REQUESTED)) {
345         if (value.isTextual()) {
346           if (hostExists(value.asText())) {
347             transfer.setRequested(value.asText());
348             try {
349               transfer.setRequester(
350                   Configuration.configuration.getHostId(value.asText()));
351             } catch (final WaarpDatabaseException e) {
352               // Ignore !!
353             }
354           } else {
355             errors.add(UNKNOWN_HOST(value.asText()));
356           }
357         } else {
358           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
359         }
360       } else if (name.equalsIgnoreCase(BLOCK_SIZE)) {
361         if (value.canConvertToInt() && value.asInt() > 0) {
362           transfer.setBlockSize(value.asInt());
363         } else {
364           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
365         }
366       } else if (name.equalsIgnoreCase(FILE_INFO)) {
367         if (value.isTextual()) {
368           transfer.setFileInfo(value.asText());
369         } else {
370           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
371         }
372       } else if (name.equalsIgnoreCase(TRANSFER_INFO)) {
373         if (value.isTextual()) {
374           transfer.setTransferInfo(value.asText());
375         } else {
376           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
377         }
378       } else if (name.equalsIgnoreCase(START)) {
379         if (value.isTextual()) {
380           try {
381             final DateTime start = DateTime.parse(value.asText());
382             if (start.isBeforeNow()) {
383               errors.add(ILLEGAL_FIELD_VALUE(name, value.asText()));
384             } else {
385               transfer.setStart(new Timestamp(start.getMillis()));
386             }
387           } catch (final IllegalArgumentException e) {
388             errors.add(ILLEGAL_FIELD_VALUE(name, value.asText()));
389           }
390         } else {
391           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
392         }
393       }
394     }
395 
396     // check that both hosts are allowed to use the transfer rule
397     final String rule = transfer.getRule();
398     final String requested = transfer.getRequested();
399     final String requester = transfer.getRequester();
400     if (rule != null && !requested.isEmpty() && canUseRule(requested, rule)) {
401       errors.add(RULE_NOT_ALLOWED(requested, rule));
402     }
403     if (rule != null && !requester.isEmpty() && canUseRule(requester, rule)) {
404       errors.add(RULE_NOT_ALLOWED(requester, rule));
405     }
406 
407     errors.addAll(checkRequiredFields(transfer));
408 
409     if (errors.isEmpty()) {
410       return transfer;
411     } else {
412       throw new RestErrorException(errors);
413     }
414   }
415 }