Skip to content

Commit

Permalink
Refine FPD (#3653)
Browse files Browse the repository at this point in the history
  • Loading branch information
CTMBNara authored Jan 8, 2025
1 parent 6b7fecb commit 663f8b5
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 624 deletions.
229 changes: 22 additions & 207 deletions src/main/java/org/prebid/server/auction/FpdResolver.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
package org.prebid.server.auction;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.App;
import com.iab.openrtb.request.Data;
import com.iab.openrtb.request.Dooh;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.User;
import org.apache.commons.lang3.ObjectUtils;
import org.prebid.server.exception.InvalidRequestException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.json.JsonMerger;
import org.prebid.server.proto.openrtb.ext.request.ExtApp;
import org.prebid.server.proto.openrtb.ext.request.ExtDooh;
import org.prebid.server.proto.openrtb.ext.request.ExtSite;
import org.prebid.server.proto.openrtb.ext.request.ExtUser;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;

public class FpdResolver {

Expand All @@ -33,17 +24,8 @@ public class FpdResolver {
private static final String APP = "app";
private static final String DOOH = "dooh";
private static final Set<String> KNOWN_FPD_ATTRIBUTES = Set.of(USER, SITE, APP, DOOH, BIDDERS);
private static final String EXT = "ext";
private static final String CONTEXT = "context";
private static final String DATA = "data";
private static final Set<String> USER_DATA_ATTR = Collections.singleton("geo");
private static final Set<String> APP_DATA_ATTR = Set.of("id", "content", "publisher", "privacypolicy");
private static final Set<String> SITE_DATA_ATTR = Set.of("id", "content", "publisher", "privacypolicy", "mobile");
private static final Set<String> DOOH_DATA_ATTR = Set.of("id", "content", "publisher", "privacypolicy");

private static final TypeReference<List<Data>> USER_DATA_TYPE_REFERENCE =
new TypeReference<>() {
};

private final JacksonMapper jacksonMapper;
private final JsonMerger jsonMerger;
Expand All @@ -54,146 +36,37 @@ public FpdResolver(JacksonMapper jacksonMapper, JsonMerger jsonMerger) {
}

public User resolveUser(User originUser, ObjectNode fpdUser) {
if (fpdUser == null) {
return originUser;
}
final User resultUser = originUser == null ? User.builder().build() : originUser;
final ExtUser resolvedExtUser = resolveUserExt(fpdUser, resultUser);
return resultUser.toBuilder()
.keywords(ObjectUtils.defaultIfNull(getString(fpdUser, "keywords"), resultUser.getKeywords()))
.gender(ObjectUtils.defaultIfNull(getString(fpdUser, "gender"), resultUser.getGender()))
.yob(ObjectUtils.defaultIfNull(getInteger(fpdUser, "yob"), resultUser.getYob()))
.data(ObjectUtils.defaultIfNull(getFpdUserData(fpdUser), resultUser.getData()))
.ext(resolvedExtUser)
.build();
}

private ExtUser resolveUserExt(ObjectNode fpdUser, User originUser) {
final ExtUser originExtUser = originUser.getExt();
final ObjectNode resolvedData =
mergeExtData(fpdUser.path(EXT).path(DATA), originExtUser != null ? originExtUser.getData() : null);

return updateUserExtDataWithFpdAttr(fpdUser, originExtUser, resolvedData);
}

private ExtUser updateUserExtDataWithFpdAttr(ObjectNode fpdUser, ExtUser originExtUser, ObjectNode extData) {
final ObjectNode resultData = extData != null ? extData : jacksonMapper.mapper().createObjectNode();
USER_DATA_ATTR.forEach(attribute -> setAttr(fpdUser, resultData, attribute));
return originExtUser != null
? originExtUser.toBuilder().data(resultData.isEmpty() ? null : resultData).build()
: resultData.isEmpty() ? null : ExtUser.builder().data(resultData).build();
}

private List<Data> getFpdUserData(ObjectNode fpdUser) {
final ArrayNode fpdUserDataNode = getValueFromJsonNode(
fpdUser, DATA, node -> (ArrayNode) node, JsonNode::isArray);

return toList(fpdUserDataNode, USER_DATA_TYPE_REFERENCE);
return mergeFpd(originUser, fpdUser, User.class);
}

public App resolveApp(App originApp, ObjectNode fpdApp) {
if (fpdApp == null) {
return originApp;
}
final App resultApp = originApp == null ? App.builder().build() : originApp;
final ExtApp resolvedExtApp = resolveAppExt(fpdApp, resultApp);
return resultApp.toBuilder()
.name(ObjectUtils.defaultIfNull(getString(fpdApp, "name"), resultApp.getName()))
.bundle(ObjectUtils.defaultIfNull(getString(fpdApp, "bundle"), resultApp.getBundle()))
.storeurl(ObjectUtils.defaultIfNull(getString(fpdApp, "storeurl"), resultApp.getStoreurl()))
.domain(ObjectUtils.defaultIfNull(getString(fpdApp, "domain"), resultApp.getDomain()))
.cat(ObjectUtils.defaultIfNull(getStrings(fpdApp, "cat"), resultApp.getCat()))
.sectioncat(ObjectUtils.defaultIfNull(getStrings(fpdApp, "sectioncat"), resultApp.getSectioncat()))
.pagecat(ObjectUtils.defaultIfNull(getStrings(fpdApp, "pagecat"), resultApp.getPagecat()))
.keywords(ObjectUtils.defaultIfNull(getString(fpdApp, "keywords"), resultApp.getKeywords()))
.ext(resolvedExtApp)
.build();
}

private ExtApp resolveAppExt(ObjectNode fpdApp, App originApp) {
final ExtApp originExtApp = originApp.getExt();
final ObjectNode resolvedData =
mergeExtData(fpdApp.path(EXT).path(DATA), originExtApp != null ? originExtApp.getData() : null);

return updateAppExtDataWithFpdAttr(fpdApp, originExtApp, resolvedData);
}

private ExtApp updateAppExtDataWithFpdAttr(ObjectNode fpdApp, ExtApp originExtApp, ObjectNode extData) {
final ObjectNode resultData = extData != null ? extData : jacksonMapper.mapper().createObjectNode();
APP_DATA_ATTR.forEach(attribute -> setAttr(fpdApp, resultData, attribute));
return originExtApp != null
? ExtApp.of(originExtApp.getPrebid(), resultData.isEmpty() ? null : resultData)
: resultData.isEmpty() ? null : ExtApp.of(null, resultData);
return mergeFpd(originApp, fpdApp, App.class);
}

public Site resolveSite(Site originSite, ObjectNode fpdSite) {
if (fpdSite == null) {
return originSite;
}
final Site resultSite = originSite == null ? Site.builder().build() : originSite;
final ExtSite resolvedExtSite = resolveSiteExt(fpdSite, resultSite);
return resultSite.toBuilder()
.name(ObjectUtils.defaultIfNull(getString(fpdSite, "name"), resultSite.getName()))
.domain(ObjectUtils.defaultIfNull(getString(fpdSite, "domain"), resultSite.getDomain()))
.cat(ObjectUtils.defaultIfNull(getStrings(fpdSite, "cat"), resultSite.getCat()))
.sectioncat(ObjectUtils.defaultIfNull(getStrings(fpdSite, "sectioncat"), resultSite.getSectioncat()))
.pagecat(ObjectUtils.defaultIfNull(getStrings(fpdSite, "pagecat"), resultSite.getPagecat()))
.page(ObjectUtils.defaultIfNull(getString(fpdSite, "page"), resultSite.getPage()))
.keywords(ObjectUtils.defaultIfNull(getString(fpdSite, "keywords"), resultSite.getKeywords()))
.ref(ObjectUtils.defaultIfNull(getString(fpdSite, "ref"), resultSite.getRef()))
.search(ObjectUtils.defaultIfNull(getString(fpdSite, "search"), resultSite.getSearch()))
.ext(resolvedExtSite)
.build();
}

private ExtSite resolveSiteExt(ObjectNode fpdSite, Site originSite) {
final ExtSite originExtSite = originSite.getExt();
final ObjectNode resolvedData =
mergeExtData(fpdSite.path(EXT).path(DATA), originExtSite != null ? originExtSite.getData() : null);

return updateSiteExtDataWithFpdAttr(fpdSite, originExtSite, resolvedData);
}

private ExtSite updateSiteExtDataWithFpdAttr(ObjectNode fpdSite, ExtSite originExtSite, ObjectNode extData) {
final ObjectNode resultData = extData != null ? extData : jacksonMapper.mapper().createObjectNode();
SITE_DATA_ATTR.forEach(attribute -> setAttr(fpdSite, resultData, attribute));
return originExtSite != null
? ExtSite.of(originExtSite.getAmp(), resultData.isEmpty() ? null : resultData)
: resultData.isEmpty() ? null : ExtSite.of(null, resultData);
return mergeFpd(originSite, fpdSite, Site.class);
}

public Dooh resolveDooh(Dooh originDooh, ObjectNode fpdDooh) {
if (fpdDooh == null) {
return originDooh;
}
final Dooh resultDooh = originDooh == null ? Dooh.builder().build() : originDooh;
final ExtDooh resolvedExtDooh = resolveDoohExt(fpdDooh, resultDooh);
return resultDooh.toBuilder()
.name(ObjectUtils.defaultIfNull(getString(fpdDooh, "name"), resultDooh.getName()))
.venuetype(ObjectUtils.defaultIfNull(getStrings(fpdDooh, "venuetype"), resultDooh.getVenuetype()))
.venuetypetax(ObjectUtils.defaultIfNull(
getInteger(fpdDooh, "venuetypetax"),
resultDooh.getVenuetypetax()))
.domain(ObjectUtils.defaultIfNull(getString(fpdDooh, "domain"), resultDooh.getDomain()))
.keywords(ObjectUtils.defaultIfNull(getString(fpdDooh, "keywords"), resultDooh.getKeywords()))
.ext(resolvedExtDooh)
.build();
return mergeFpd(originDooh, fpdDooh, Dooh.class);
}

private ExtDooh resolveDoohExt(ObjectNode fpdDooh, Dooh originDooh) {
final ExtDooh originExtDooh = originDooh.getExt();
final ObjectNode resolvedData =
mergeExtData(fpdDooh.path(EXT).path(DATA), originExtDooh != null ? originExtDooh.getData() : null);
private <T> T mergeFpd(T original, ObjectNode fpd, Class<T> tClass) {
if (fpd == null || fpd.isNull() || fpd.isMissingNode()) {
return original;
}

return updateDoohExtDataWithFpdAttr(fpdDooh, originExtDooh, resolvedData);
}
final ObjectMapper mapper = jacksonMapper.mapper();

private ExtDooh updateDoohExtDataWithFpdAttr(ObjectNode fpdDooh, ExtDooh originExtDooh, ObjectNode extData) {
final ObjectNode resultData = extData != null ? extData : jacksonMapper.mapper().createObjectNode();
DOOH_DATA_ATTR.forEach(attribute -> setAttr(fpdDooh, resultData, attribute));
return originExtDooh != null
? ExtDooh.of(resultData.isEmpty() ? null : resultData)
: resultData.isEmpty() ? null : ExtDooh.of(resultData);
final JsonNode originalAsJsonNode = original != null
? mapper.valueToTree(original)
: NullNode.getInstance();
final JsonNode merged = jsonMerger.merge(fpd, originalAsJsonNode);
try {
return mapper.treeToValue(merged, tClass);
} catch (JsonProcessingException e) {
throw new InvalidRequestException("Can't convert merging result class " + tClass.getName());
}
}

public ObjectNode resolveImpExt(ObjectNode impExt, ObjectNode targeting) {
Expand Down Expand Up @@ -277,62 +150,4 @@ private void removeOrReplace(ObjectNode impExt, String field, JsonNode jsonNode)
impExt.set(field, jsonNode);
}
}

private ObjectNode mergeExtData(JsonNode fpdData, JsonNode originData) {
if (fpdData.isMissingNode() || !fpdData.isObject()) {
return originData != null && originData.isObject() ? ((ObjectNode) originData).deepCopy() : null;
}

if (originData != null && originData.isObject()) {
return (ObjectNode) jsonMerger.merge(fpdData, originData);
}
return fpdData.isObject() ? (ObjectNode) fpdData : null;
}

private static void setAttr(ObjectNode source, ObjectNode dest, String fieldName) {
final JsonNode field = source.get(fieldName);
if (field != null) {
dest.set(fieldName, field);
}
}

private static List<String> getStrings(JsonNode firstItem, String fieldName) {
final JsonNode valueNode = firstItem.get(fieldName);
final ArrayNode arrayNode = valueNode != null && valueNode.isArray() ? (ArrayNode) valueNode : null;
return arrayNode != null && isTextualArray(arrayNode)
? StreamSupport.stream(arrayNode.spliterator(), false)
.map(JsonNode::asText)
.toList()
: null;
}

private static boolean isTextualArray(ArrayNode arrayNode) {
return StreamSupport.stream(arrayNode.spliterator(), false).allMatch(JsonNode::isTextual);
}

private static String getString(ObjectNode firstItem, String fieldName) {
return getValueFromJsonNode(firstItem, fieldName, JsonNode::asText, JsonNode::isTextual);
}

private static Integer getInteger(ObjectNode firstItem, String fieldName) {
return getValueFromJsonNode(firstItem, fieldName, JsonNode::asInt, JsonNode::isInt);
}

private <T> List<T> toList(JsonNode node, TypeReference<List<T>> listTypeReference) {
try {
return jacksonMapper.mapper().convertValue(node, listTypeReference);
} catch (IllegalArgumentException e) {
return null;
}
}

private static <T> T getValueFromJsonNode(ObjectNode firstItem, String fieldName,
Function<JsonNode, T> nodeConverter,
Predicate<JsonNode> isCorrectType) {
final JsonNode valueNode = firstItem.get(fieldName);
return valueNode != null && isCorrectType.test(valueNode)
? nodeConverter.apply(valueNode)
: null;
}

}
Loading

0 comments on commit 663f8b5

Please sign in to comment.