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}