Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 54 additions & 100 deletions src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java
Comment thread
CTMBNara marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • moved methods in call order
  • simplified code
  • imp.ext and imp.ext.bidder can't be null on this stage
  • optimised makeImpExt
package org.prebid.server.bidder.openx;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.bidder.openx.proto.OpenxBidExt;
import org.prebid.server.bidder.openx.proto.OpenxRequestExt;
import org.prebid.server.bidder.openx.proto.OpenxVideoExt;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.openx.ExtImpOpenx;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class OpenxBidder implements Bidder<BidRequest> {

    private static final String OPENX_CONFIG = "hb_pbs_1.0.0";
    private static final String DEFAULT_BID_CURRENCY = "USD";
    private static final String CUSTOM_PARAMS_KEY = "customParams";
    private static final String BIDDER_EXT = "bidder";
    private static final String PREBID_EXT = "prebid";
    private static final Set<String> IMP_EXT_SKIP_FIELDS = Set.of(BIDDER_EXT, PREBID_EXT);

    private static final TypeReference<ExtPrebid<ExtImpPrebid, ExtImpOpenx>> OPENX_EXT_TYPE_REFERENCE =
            new TypeReference<>() {
            };

    private final String endpointUrl;
    private final JacksonMapper mapper;

    public OpenxBidder(String endpointUrl, JacksonMapper mapper) {
        this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
        this.mapper = Objects.requireNonNull(mapper);
    }

    @Override
    public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
        final List<Imp> modifiedImps = new ArrayList<>();
        final List<BidderError> errors = new ArrayList<>();
        ExtImpOpenx firstValidImpExt = null;

        for (Imp imp : bidRequest.getImp()) {
            if (!isSupportedImpType(imp)) {
                errors.add(BidderError.badInput(
                        "OpenX only supports banner, video and native imps. Ignoring imp id=" + imp.getId()));
                continue;
            }

            final ExtPrebid<ExtImpPrebid, ExtImpOpenx> impExt;
            try {
                impExt = parseImpExt(imp);
            } catch (PreBidException e) {
                errors.add(BidderError.badInput("imp id=%s: %s".formatted(imp.getId(), e.getMessage())));
                continue;
            }

            modifiedImps.add(makeImp(imp, impExt));
            if (firstValidImpExt == null) {
                firstValidImpExt = impExt.getBidder();
            }
        }

        if (modifiedImps.isEmpty()) {
            return Result.withErrors(errors);
        }

        final BidRequest modifiedBidRequest = modifyBidRequest(bidRequest, modifiedImps, firstValidImpExt);

        return Result.of(
                Collections.singletonList(BidderUtil.defaultRequest(modifiedBidRequest, endpointUrl, mapper)),
                errors);
    }

    private static boolean isSupportedImpType(Imp imp) {
        return imp.getBanner() != null || imp.getVideo() != null || imp.getXNative() != null;
    }

    private ExtPrebid<ExtImpPrebid, ExtImpOpenx> parseImpExt(Imp imp) {
        try {
            return mapper.mapper().convertValue(imp.getExt(), OPENX_EXT_TYPE_REFERENCE);
        } catch (IllegalArgumentException e) {
            throw new PreBidException(e.getMessage());
        }
    }

    private Imp makeImp(Imp imp, ExtPrebid<ExtImpPrebid, ExtImpOpenx> impExt) {
        final ExtImpOpenx openxImpExt = impExt.getBidder();
        final ExtImpPrebid prebidImpExt = impExt.getPrebid();

        final Imp.ImpBuilder impBuilder = imp.toBuilder()
                .tagid(openxImpExt.getUnit())
                .bidfloor(resolveBidFloor(imp.getBidfloor(), openxImpExt.getCustomFloor()))
                .ext(makeImpExt(imp.getExt(), MapUtils.isNotEmpty(openxImpExt.getCustomParams())));

        if (imp.getVideo() != null
                && prebidImpExt != null
                && Objects.equals(prebidImpExt.getIsRewardedInventory(), 1)) {

            impBuilder.video(imp.getVideo().toBuilder()
                    .ext(mapper.mapper().valueToTree(OpenxVideoExt.of(1)))
                    .build());
        }

        return impBuilder.build();
    }

    private static BigDecimal resolveBidFloor(BigDecimal impBidFloor, BigDecimal customFloor) {
        return !BidderUtil.isValidPrice(impBidFloor) && BidderUtil.isValidPrice(customFloor)
                ? customFloor
                : impBidFloor;
    }

    private ObjectNode makeImpExt(ObjectNode impExt, boolean addCustomParams) {
        final ObjectNode openxImpExt = impExt.deepCopy();
        if (addCustomParams) {
            openxImpExt.set(CUSTOM_PARAMS_KEY, openxImpExt.get(BIDDER_EXT).get(CUSTOM_PARAMS_KEY));
        }
        openxImpExt.remove(IMP_EXT_SKIP_FIELDS);

        return openxImpExt;
    }

    private BidRequest modifyBidRequest(BidRequest bidRequest, List<Imp> imps, ExtImpOpenx openxImpExt) {
        return bidRequest.toBuilder()
                .imp(imps)
                .ext(makeReqExt(openxImpExt))
                .build();
    }

    private ExtRequest makeReqExt(ExtImpOpenx openxImpExt) {
        return mapper.fillExtension(
                ExtRequest.empty(),
                OpenxRequestExt.of(openxImpExt.getDelDomain(), openxImpExt.getPlatform(), OPENX_CONFIG));
    }

    @Override
    public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
        try {
            final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
            return Result.withValues(extractBids(bidRequest, bidResponse));
        } catch (DecodeException e) {
            return Result.withError(BidderError.badServerResponse(e.getMessage()));
        }
    }

    private List<BidderBid> extractBids(BidRequest bidRequest, BidResponse bidResponse) {
        if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
            return Collections.emptyList();
        }

        final Map<String, BidType> impIdToBidType = impIdToBidType(bidRequest);

        final String bidCurrency = StringUtils.defaultIfBlank(bidResponse.getCur(), DEFAULT_BID_CURRENCY);

        return bidResponse.getSeatbid().stream()
                .filter(Objects::nonNull)
                .map(SeatBid::getBid)
                .filter(Objects::nonNull)
                .flatMap(Collection::stream)
                .filter(Objects::nonNull)
                .map(bid -> toBidderBid(bid, impIdToBidType, bidCurrency))
                .toList();
    }

    private static Map<String, BidType> impIdToBidType(BidRequest bidRequest) {
        return bidRequest.getImp().stream()
                .collect(Collectors.toMap(Imp::getId, OpenxBidder::resolveBidType));
    }

    private static BidType resolveBidType(Imp imp) {
        if (imp.getBanner() != null) {
            return BidType.banner;
        }
        if (imp.getVideo() != null) {
            return BidType.video;
        }
        if (imp.getXNative() != null) {
            return BidType.xNative;
        }
        return BidType.banner;
    }

    private BidderBid toBidderBid(Bid bid, Map<String, BidType> impIdToBidType, String bidCurrency) {
        final BidType bidType = getBidType(bid, impIdToBidType);
        final ExtBidPrebidVideo videoInfo = bidType == BidType.video ? getVideoInfo(bid) : null;

        return BidderBid.builder()
                .bid(bid.toBuilder().ext(getBidExt(bid)).build())
                .type(bidType)
                .bidCurrency(bidCurrency)
                .videoInfo(videoInfo)
                .build();
    }

    private static BidType getBidType(Bid bid, Map<String, BidType> impIdToBidType) {
        return switch (bid.getMtype()) {
            case 1 -> BidType.banner;
            case 2 -> BidType.video;
            case 4 -> BidType.xNative;
            case null, default -> impIdToBidType.getOrDefault(bid.getImpid(), BidType.banner);
        };
    }

    private static ExtBidPrebidVideo getVideoInfo(Bid bid) {
        return ExtBidPrebidVideo.of(
                bid.getDur(),
                CollectionUtils.isEmpty(bid.getCat()) ? null : bid.getCat().getFirst());
    }

    private ObjectNode getBidExt(Bid bid) {
        final ObjectNode ext = bid.getExt();
        if (ext == null) {
            return null;
        }

        final OpenxBidExt openxBidExt = parseOpenxBidExt(ext);
        final Integer buyerId = parseStringToInt(openxBidExt.getBuyerId());
        final Integer dspId = parseStringToInt(openxBidExt.getDspId());
        final Integer brandId = parseStringToInt(openxBidExt.getBrandId());

        if (buyerId == null && dspId == null && brandId == null) {
            return ext;
        }

        final ExtBidPrebidMeta meta = ExtBidPrebidMeta.builder()
                .networkId(dspId)
                .advertiserId(buyerId)
                .brandId(brandId)
                .build();

        final ExtBidPrebid extBidPrebid = ExtBidPrebid.builder().meta(meta).build();

        ext.set(PREBID_EXT, mapper.mapper().valueToTree(extBidPrebid));

        return ext;
    }

    private OpenxBidExt parseOpenxBidExt(ObjectNode ext) {
        try {
            return mapper.mapper().convertValue(ext, OpenxBidExt.class);
        } catch (IllegalArgumentException e) {
            return OpenxBidExt.builder().build();
        }
    }

    private static Integer parseStringToInt(String value) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return null;
        }
    }
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.bidder.openx.model.OpenxImpType;
import org.prebid.server.bidder.openx.proto.OpenxBidExt;
import org.prebid.server.bidder.openx.proto.OpenxRequestExt;
import org.prebid.server.bidder.openx.proto.OpenxVideoExt;
Expand All @@ -43,7 +42,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OpenxBidder implements Bidder<BidRequest> {

Expand All @@ -68,20 +66,56 @@ public OpenxBidder(String endpointUrl, JacksonMapper mapper) {

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
final Map<OpenxImpType, List<Imp>> differentiatedImps = bidRequest.getImp().stream()
.collect(Collectors.groupingBy(OpenxBidder::resolveImpType));
final List<Imp> modifiedImps = new ArrayList<>();
final List<BidderError> errors = new ArrayList<>();
final ExtImpOpenx firstValidImpExt = processImps(bidRequest.getImp(), modifiedImps, errors);

if (modifiedImps.isEmpty()) {
return Result.withErrors(errors);
}

final List<BidderError> processingErrors = new ArrayList<>();
final List<BidRequest> outgoingRequests = makeRequests(
bidRequest,
differentiatedImps.get(OpenxImpType.banner),
differentiatedImps.get(OpenxImpType.video),
differentiatedImps.get(OpenxImpType.xNative),
processingErrors);
final BidRequest modifiedBidRequest = modifyBidRequest(bidRequest, modifiedImps, firstValidImpExt);
return Result.of(Collections.singletonList(makeRequest(modifiedBidRequest)), errors);
}

final List<BidderError> errors = errors(differentiatedImps.get(OpenxImpType.other), processingErrors);
private ExtImpOpenx processImps(List<Imp> imps, List<Imp> modifiedImps, List<BidderError> errors) {
ExtImpOpenx firstValidImpExt = null;
for (Imp imp : imps) {
if (!isSupportedImpType(imp)) {
errors.add(unsupportedImpTypeError(imp));
continue;
}

final ExtPrebid<ExtImpPrebid, ExtImpOpenx> impExt;
try {
impExt = parseOpenxExt(imp);
} catch (PreBidException e) {
errors.add(invalidImpError(imp, e));
continue;
}

modifiedImps.add(makeImp(imp, impExt));
if (firstValidImpExt == null) {
firstValidImpExt = impExt.getBidder();
}
}
return firstValidImpExt;
}

return Result.of(createHttpRequests(outgoingRequests), errors);
private static BidderError unsupportedImpTypeError(Imp imp) {
return BidderError.badInput(
"OpenX only supports banner, video and native imps. Ignoring imp id=" + imp.getId());
}

private static BidderError invalidImpError(Imp imp, PreBidException e) {
return BidderError.badInput("imp id=%s: %s".formatted(imp.getId(), e.getMessage()));
}

private BidRequest modifyBidRequest(BidRequest bidRequest, List<Imp> imps, ExtImpOpenx firstValidImpExt) {
return bidRequest.toBuilder()
.imp(imps)
.ext(makeReqExt(firstValidImpExt))
.build();
}

@Override
Expand All @@ -94,45 +128,8 @@ public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequ
}
}

private List<BidRequest> makeRequests(
BidRequest bidRequest,
List<Imp> bannerImps,
List<Imp> videoImps,
List<Imp> nativeImps,
List<BidderError> errors) {
final List<BidRequest> bidRequests = new ArrayList<>();
// single request for all banner and native imps
final List<Imp> bannerAndNativeImps = Stream.of(bannerImps, nativeImps)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
final BidRequest bannerAndNativeImpsRequest = createSingleRequest(bannerAndNativeImps, bidRequest, errors);
if (bannerAndNativeImpsRequest != null) {
bidRequests.add(bannerAndNativeImpsRequest);
}

if (CollectionUtils.isNotEmpty(videoImps)) {
// single request for each video imp
bidRequests.addAll(videoImps.stream()
.map(Collections::singletonList)
.map(imps -> createSingleRequest(imps, bidRequest, errors))
.filter(Objects::nonNull)
.toList());
}
return bidRequests;
}

private static OpenxImpType resolveImpType(Imp imp) {
if (imp.getBanner() != null) {
return OpenxImpType.banner;
}
if (imp.getVideo() != null) {
return OpenxImpType.video;
}
if (imp.getXNative() != null) {
return OpenxImpType.xNative;
}
return OpenxImpType.other;
private static boolean isSupportedImpType(Imp imp) {
return imp.getBanner() != null || imp.getVideo() != null || imp.getXNative() != null;
}

private static BidType resolveBidType(Imp imp) {
Expand All @@ -148,61 +145,19 @@ private static BidType resolveBidType(Imp imp) {
return BidType.banner;
}

private List<BidderError> errors(List<Imp> notSupportedImps, List<BidderError> processingErrors) {
final List<BidderError> errors = new ArrayList<>();
// add errors for imps with unsupported media types
if (CollectionUtils.isNotEmpty(notSupportedImps)) {
errors.addAll(
notSupportedImps.stream()
.map(imp ->
"OpenX only supports banner, video and native imps. Ignoring imp id=" + imp.getId())
.map(BidderError::badInput)
.toList());
}

// add errors detected during requests creation
errors.addAll(processingErrors);

return errors;
}

private List<HttpRequest<BidRequest>> createHttpRequests(List<BidRequest> bidRequests) {
return bidRequests.stream()
.filter(Objects::nonNull)
.map(singleBidRequest -> BidderUtil.defaultRequest(singleBidRequest, endpointUrl, mapper))
.toList();
}

private BidRequest createSingleRequest(List<Imp> imps, BidRequest bidRequest, List<BidderError> errors) {
if (CollectionUtils.isEmpty(imps)) {
return null;
}

List<Imp> processedImps = null;
try {
processedImps = imps.stream().map(this::makeImp).toList();
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}

return CollectionUtils.isNotEmpty(processedImps)
? bidRequest.toBuilder()
.imp(processedImps)
.ext(makeReqExt(imps.getFirst()))
.build()
: null;
private HttpRequest<BidRequest> makeRequest(BidRequest bidRequest) {
return BidderUtil.defaultRequest(bidRequest, endpointUrl, mapper);
}

private Imp makeImp(Imp imp) {
final ExtPrebid<ExtImpPrebid, ExtImpOpenx> impExt = parseOpenxExt(imp);
private Imp makeImp(Imp imp, ExtPrebid<ExtImpPrebid, ExtImpOpenx> impExt) {
final ExtImpOpenx openxImpExt = impExt.getBidder();
final ExtImpPrebid prebidImpExt = impExt.getPrebid();
final Imp.ImpBuilder impBuilder = imp.toBuilder()
.tagid(openxImpExt.getUnit())
.bidfloor(resolveBidFloor(imp.getBidfloor(), openxImpExt.getCustomFloor()))
.ext(makeImpExt(imp.getExt(), MapUtils.isNotEmpty(openxImpExt.getCustomParams())));

if (resolveImpType(imp) == OpenxImpType.video
if (imp.getVideo() != null
&& prebidImpExt != null
&& Objects.equals(prebidImpExt.getIsRewardedInventory(), 1)) {
impBuilder.video(imp.getVideo().toBuilder()
Expand All @@ -218,8 +173,7 @@ private static BigDecimal resolveBidFloor(BigDecimal impBidFloor, BigDecimal cus
: impBidFloor;
}

private ExtRequest makeReqExt(Imp imp) {
final ExtImpOpenx openxImpExt = parseOpenxExt(imp).getBidder();
private ExtRequest makeReqExt(ExtImpOpenx openxImpExt) {
return mapper.fillExtension(
ExtRequest.empty(),
OpenxRequestExt.of(openxImpExt.getDelDomain(), openxImpExt.getPlatform(), OPENX_CONFIG));
Expand Down

This file was deleted.

Loading
Loading