001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.api.jackson;
015
016import org.gbif.api.model.occurrence.DownloadFormat;
017import org.gbif.api.model.occurrence.DownloadRequest;
018import org.gbif.api.model.occurrence.DownloadType;
019import org.gbif.api.model.occurrence.PredicateDownloadRequest;
020import org.gbif.api.model.predicate.Predicate;
021import org.gbif.api.util.VocabularyUtils;
022import org.gbif.api.vocabulary.Extension;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.List;
029import java.util.Optional;
030import java.util.Set;
031import java.util.stream.Collectors;
032
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import com.fasterxml.jackson.core.JsonParser;
037import com.fasterxml.jackson.databind.DeserializationContext;
038import com.fasterxml.jackson.databind.JsonDeserializer;
039import com.fasterxml.jackson.databind.JsonNode;
040import com.fasterxml.jackson.databind.ObjectMapper;
041
042/**
043 * Download request deserializer.
044 * <p>
045 * For most of the time, the serialization has been to "notificationAddresses" and
046 * "sendNotification".  For a few months in 2018-2019, it was "notification_address" and
047 * "send_notification".
048 * <p>
049 * The API documentation has previously specified "notification_address" and "sendNotification".
050 * <p>
051 * We therefore accept all combinations.
052 * <p>
053 * https://github.com/gbif/portal-feedback/issues/2046
054 */
055public class DownloadRequestSerde extends JsonDeserializer<DownloadRequest> {
056
057  private static final String PREDICATE = "predicate";
058  private static final List<String> SEND_NOTIFICATION = Collections
059    .unmodifiableList(Arrays.asList("sendNotification", "send_notification"));
060  private static final List<String> NOTIFICATION_ADDRESSES =
061    Collections.unmodifiableList(
062      Arrays.asList("notificationAddresses", "notificationAddress", "notification_addresses",
063        "notification_address"));
064  private static final String CREATOR = "creator";
065  private static final String FORMAT = "format";
066  private static final String TYPE = "type";
067  private static final String VERBATIM_EXTENSIONS = "verbatimExtensions";
068  private static final Logger LOG = LoggerFactory.getLogger(DownloadRequestSerde.class);
069  private static final ObjectMapper MAPPER = new ObjectMapper();
070
071  @Override
072  public DownloadRequest deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
073    JsonNode node = jp.getCodec().readTree(jp);
074    LOG.debug("DownloadRequest for deserialization: {}", node);
075    //at least one element must be defined
076    if (node.size() == 0) {
077      return null;
078    }
079    DownloadFormat format = Optional.ofNullable(node.get(FORMAT))
080      .map(n -> VocabularyUtils.lookupEnum(n.asText(), DownloadFormat.class)).orElse(DownloadFormat.DWCA);
081
082    DownloadType type = Optional.ofNullable(node.get(TYPE))
083      .map(n -> VocabularyUtils.lookupEnum(n.asText(), DownloadType.class)).orElse(DownloadType.OCCURRENCE);
084
085    String creator = Optional.ofNullable(node.get(CREATOR)).map(JsonNode::asText).orElse(null);
086
087    List<String> notificationAddresses = new ArrayList<>();
088    for (final String jsonKey : NOTIFICATION_ADDRESSES) {
089      notificationAddresses.addAll(Optional.ofNullable(node.get(jsonKey)).map(jsonNode -> {
090        try {
091          return Arrays.asList(MAPPER.treeToValue(jsonNode, String[].class));
092        } catch (Exception e) {
093          throw new RuntimeException(e);
094        }
095      }).orElse(new ArrayList<>()));
096    }
097
098    boolean sendNotification = false;
099    for (final String jsonKey : SEND_NOTIFICATION) {
100      sendNotification |= Optional.ofNullable(node.get(jsonKey)).map(JsonNode::asBoolean).orElse(Boolean.FALSE);
101    }
102
103    JsonNode predicate = Optional.ofNullable(node.get(PREDICATE)).orElse(null);
104    Predicate predicateObj = predicate == null ? null : MAPPER.treeToValue(predicate, Predicate.class);
105
106    Set<Extension> extensions = Optional.ofNullable(node.get(VERBATIM_EXTENSIONS)).map(jsonNode -> {
107      try {
108        return Arrays.stream(MAPPER.treeToValue(jsonNode, String[].class))
109                .map(Extension::fromRowType)
110                .collect(Collectors.toSet());
111      } catch (Exception e) {
112        throw new RuntimeException(e);
113      }
114    }).orElse(Collections.emptySet());
115
116    return new PredicateDownloadRequest(predicateObj, creator, notificationAddresses, sendNotification, format, type, extensions);
117  }
118}