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.waarp.common.database.exception.WaarpDatabaseSqlException;
26  import org.waarp.common.json.JsonHandler;
27  import org.waarp.common.logging.SysErrLogger;
28  import org.waarp.common.utility.ParametersChecker;
29  import org.waarp.common.utility.WaarpStringUtils;
30  import org.waarp.openr66.pojo.Host;
31  import org.waarp.openr66.protocol.http.restv2.errors.RestError;
32  import org.waarp.openr66.protocol.http.restv2.errors.RestErrorException;
33  
34  import javax.ws.rs.InternalServerErrorException;
35  import java.util.ArrayList;
36  import java.util.Comparator;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  
41  import static org.waarp.common.file.FileUtils.*;
42  import static org.waarp.openr66.protocol.configuration.Configuration.*;
43  import static org.waarp.openr66.protocol.http.restv2.RestConstants.HostFields.*;
44  import static org.waarp.openr66.protocol.http.restv2.errors.RestErrors.*;
45  
46  /**
47   * A collection of utility methods to convert {@link Host} objects to their
48   * corresponding {@link ObjectNode}
49   * and vice-versa.
50   */
51  public final class HostConverter {
52  
53    /**
54     * Makes the default constructor of this utility class inaccessible.
55     */
56    private HostConverter() throws InstantiationException {
57      throw new InstantiationException(
58          getClass().getName() + " cannot be instantiated.");
59    }
60  
61    // ########################### INNER CLASSES ################################
62  
63    /**
64     * Represents all the possible ways to sort a list of host objects.
65     */
66    public enum Order {
67      /**
68       * By hostID, in ascending order.
69       */
70      ascId(new Comparator<Host>() {
71        @Override
72        public final int compare(final Host t1, final Host t2) {
73          return t1.getHostid().compareTo(t2.getHostid());
74        }
75      }),
76      /**
77       * By hostID, in descending order.
78       */
79      descId(new Comparator<Host>() {
80        @Override
81        public final int compare(final Host t1, final Host t2) {
82          return -t1.getHostid().compareTo(t2.getHostid());//NOSONAR
83        }
84      }),
85      /**
86       * By address, in ascending order.
87       */
88      ascAddress(new Comparator<Host>() {
89        @Override
90        public final int compare(final Host t1, final Host t2) {
91          return t1.getAddress().compareTo(t2.getAddress());//NOSONAR
92        }
93      }),
94      /**
95       * By address, in descending order.
96       */
97      descAddress(new Comparator<Host>() {
98        @Override
99        public final int compare(final Host t1, final Host t2) {
100         return -t1.getAddress().compareTo(t2.getAddress());//NOSONAR
101       }
102     });
103 
104     /**
105      * The comparator used to sort the list of RestHost objects.
106      */
107     public final Comparator<Host> comparator;
108 
109     Order(final Comparator<Host> comparator) {
110       this.comparator = comparator;
111     }
112   }
113 
114   // ########################### PUBLIC METHODS ###############################
115 
116   /**
117    * Converts the given {@link Host} object into an {@link ObjectNode}.
118    *
119    * @param host the host to convert
120    *
121    * @return the converted ObjectNode
122    */
123   public static ObjectNode hostToNode(final Host host) {
124     final ObjectNode node = JsonHandler.createObjectNode();
125     node.put(HOST_NAME, host.getHostid());
126     node.put(ADDRESS, host.getAddress());
127     node.put(PORT, host.getPort());
128     node.put(PASSWORD, host.getHostkey());
129     node.put(IS_SSL, host.isSSL());
130     node.put(IS_CLIENT, host.isClient());
131     node.put(IS_ADMIN, host.isAdmin());
132     node.put(IS_ACTIVE, host.isActive());
133     node.put(IS_PROXY, host.isProxified());
134 
135     return node;
136   }
137 
138   /**
139    * Converts the given {@link ObjectNode} into a {@link Host} object.
140    *
141    * @param object the ObjectNode to convert
142    *
143    * @return the corresponding Host object
144    *
145    * @throws RestErrorException if the given ObjectNode does not
146    *     represent a Host object
147    * @throws InternalServerErrorException if an unexpected error
148    *     occurred
149    */
150   public static Host nodeToNewHost(final ObjectNode object) {
151     Host emptyHost = null;
152     try {
153       emptyHost =
154           new Host(null, null, -1, null, false, false, false, false, true);
155     } catch (final WaarpDatabaseSqlException e) {
156       SysErrLogger.FAKE_LOGGER.syserr(e);
157     }
158 
159     return nodeToUpdatedHost(object, emptyHost);
160   }
161 
162   /**
163    * Returns the given {@link Host} object updated with the values defined in
164    * the {@link ObjectNode} parameter.
165    * All fields missing in the JSON object will stay unchanged in the updated
166    * host entry.
167    *
168    * @param object the ObjectNode to convert.
169    * @param oldHost the host entry to update.
170    *
171    * @return the updated host entry
172    *
173    * @throws RestErrorException if the given ObjectNode does not
174    *     represent a Host object
175    * @throws InternalServerErrorException if an unexpected error
176    *     occurred
177    */
178   public static Host nodeToUpdatedHost(final ObjectNode object,
179                                        final Host oldHost) {
180 
181     final List<RestError> errors = new ArrayList<RestError>();
182 
183     final Iterator<Map.Entry<String, JsonNode>> fields = object.fields();
184     while (fields.hasNext()) {
185       final Map.Entry<String, JsonNode> field = fields.next();
186       final String name = field.getKey();
187       final JsonNode value = field.getValue();
188 
189       if (name.equalsIgnoreCase(HOST_NAME)) {
190         if (value.isTextual()) {
191           if (oldHost.getHostid() == null) {
192             oldHost.setHostid(value.asText());
193           } else if (!oldHost.getHostid().equals(value.asText())) {
194             errors.add(FIELD_NOT_ALLOWED(HOST_NAME));
195           }
196         } else {
197           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
198         }
199       } else if (name.equalsIgnoreCase(ADDRESS)) {
200         if (value.isTextual()) {
201           oldHost.setAddress(value.asText());
202         } else {
203           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
204         }
205       } else if (name.equalsIgnoreCase(PORT)) {
206         if (value.canConvertToInt() && value.asInt() >= 0 &&
207             value.asInt() < ZERO_COPY_CHUNK_SIZE) {
208           oldHost.setPort(value.asInt());
209         } else {
210           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
211         }
212       } else if (name.equalsIgnoreCase(PASSWORD)) {
213         if (value.isTextual()) {
214           oldHost.setHostkey(encryptPassword(value.asText()));
215         } else {
216           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
217         }
218       } else if (name.equalsIgnoreCase(IS_SSL)) {
219         if (value.isBoolean()) {
220           oldHost.setSSL(value.asBoolean());
221         } else {
222           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
223         }
224       } else if (name.equalsIgnoreCase(IS_CLIENT)) {
225         if (value.isBoolean()) {
226           oldHost.setClient(value.asBoolean());
227         } else {
228           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
229         }
230       } else if (name.equalsIgnoreCase(IS_ADMIN)) {
231         if (value.isBoolean()) {
232           oldHost.setAdmin(value.asBoolean());
233         } else {
234           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
235         }
236       } else if (name.equalsIgnoreCase(IS_ACTIVE)) {
237         if (value.isBoolean()) {
238           oldHost.setActive(value.asBoolean());
239         } else {
240           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
241         }
242       } else if (name.equalsIgnoreCase(IS_PROXY)) {
243         if (value.isBoolean()) {
244           oldHost.setProxified(value.asBoolean());
245         } else {
246           errors.add(ILLEGAL_FIELD_VALUE(name, value.toString()));
247         }
248       } else {
249         errors.add(UNKNOWN_FIELD(name));
250       }
251     }
252 
253     errors.addAll(checkRequiredFields(oldHost));
254 
255     if (errors.isEmpty()) {
256       return oldHost;
257     } else {
258       throw new RestErrorException(errors);
259     }
260   }
261 
262   // ########################## PRIVATE METHODS ###############################
263 
264   /**
265    * Encrypts the given password String using the server's cryptographic key.
266    *
267    * @param password the password to encrypt
268    *
269    * @return the encrypted password
270    *
271    * @throws InternalServerErrorException If an error occurred when
272    *     encrypting the password.
273    */
274   private static byte[] encryptPassword(final String password) {
275     try {
276       return configuration.getCryptoKey().cryptToHex(password)
277                           .getBytes(WaarpStringUtils.UTF8);
278     } catch (final Exception e) {
279       throw new InternalServerErrorException(
280           "Failed to encrypt the host password", e);
281     }
282   }
283 
284   /**
285    * List all missing required fields. This method returns a list of {@link
286    * RestError} representing all the
287    * errors encountered when checking the given host's required fields. If all
288    * required fields have indeed been
289    * initialized, an empty list is returned.
290    *
291    * @param host the host entry to check
292    *
293    * @return the list of encountered errors
294    */
295   private static List<RestError> checkRequiredFields(final Host host) {
296     final List<RestError> errors = new ArrayList<RestError>();
297     if (ParametersChecker.isEmpty(host.getHostid())) {
298       errors.add(MISSING_FIELD(HOST_NAME));
299     }
300     if (ParametersChecker.isEmpty(host.getAddress())) {
301       errors.add(MISSING_FIELD(ADDRESS));
302     }
303     if (host.getPort() == -1) {
304       errors.add(MISSING_FIELD(PORT));
305     }
306     if (host.getHostkey() == null || host.getHostkey().length == 0) {
307       errors.add(MISSING_FIELD(PASSWORD));
308     }
309 
310     return errors;
311   }
312 }