From cd43c2392327c334c5a6b4a2c54e809150bb3013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Tue, 28 Apr 2015 14:43:05 +0200 Subject: [PATCH 01/28] Marketing API: Ads support for account operations and account insights. --- .../facebook/api/AccountOperations.java | 123 +++++ .../social/facebook/api/AdAccount.java | 265 ++++++++++ .../social/facebook/api/AdAccountGroup.java | 24 + .../social/facebook/api/AdInsight.java | 252 ++++++++++ .../social/facebook/api/AdInsightAction.java | 19 + .../social/facebook/api/AdUser.java | 39 ++ .../social/facebook/api/FacebookAds.java | 13 + .../facebook/api/impl/AccountTemplate.java | 102 ++++ .../api/impl/FacebookAdsTemplate.java | 32 ++ .../api/impl/json/AdAccountGroupMixin.java | 21 + .../api/impl/json/AdAccountMixin.java | 276 +++++++++++ .../api/impl/json/AdInsightActionMixin.java | 16 + .../api/impl/json/AdInsightMixin.java | 159 ++++++ .../facebook/api/impl/json/AdUserMixin.java | 103 ++++ .../api/impl/json/FacebookModule.java | 17 +- .../api/AbstractFacebookAdsApiTest.java | 47 ++ .../facebook/api/AccountTemplateTest.java | 469 ++++++++++++++++++ .../facebook/api/ad-account-insights.json | 106 ++++ .../facebook/api/ad-account-no-users.json | 44 ++ .../ad-account-temporarily-unavailable.json | 53 ++ .../api/ad-account-unknown-capabilities.json | 10 + .../social/facebook/api/ad-account-users.json | 34 ++ .../facebook/api/ad-account-with-agency.json | 67 +++ .../api/ad-account-with-few-users.json | 61 +++ .../api/ad-account-with-funding-details.json | 58 +++ .../api/ad-account-with-tos-accepted.json | 57 +++ .../api/ad-account-without-permission.json | 50 ++ .../social/facebook/api/ad-account.json | 53 ++ .../social/facebook/api/ad-accounts.json | 122 +++++ 29 files changed, 2691 insertions(+), 1 deletion(-) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccount.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccountGroup.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsight.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsightAction.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdUser.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountGroupMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightActionMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdUserMixin.java create mode 100644 spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AbstractFacebookAdsApiTest.java create mode 100644 spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-insights.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-no-users.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-temporarily-unavailable.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-unknown-capabilities.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-users.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-agency.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-few-users.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-funding-details.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-tos-accepted.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-without-permission.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-accounts.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java new file mode 100644 index 000000000..588884af9 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java @@ -0,0 +1,123 @@ +package org.springframework.social.facebook.api; + +import org.springframework.social.ApiException; +import org.springframework.social.InsufficientPermissionException; +import org.springframework.social.MissingAuthorizationException; +import org.springframework.social.facebook.api.AdUser.AdUserRole; + +import java.util.List; + +/** + * Defines operations for working with Facebook Marketing API Ad Account object. + * + * @author Sebastian Górecki + */ +public interface AccountOperations { + + static final String[] AD_ACCOUNT_FIELDS = { + "id", "account_id", "account_status", "age", "amount_spent", "balance", "business_city", "business_country_code", + "business_name", "business_state", "business_street", "business_street2", "business_zip", "capabilities", + "created_time", "currency", "daily_spend_limit", "end_advertiser", "funding_source", "funding_source_details", + "is_personal", "media_agency", "name", "offsite_pixels_tos_accepted", "partner", "spend_cap", "timezone_id", + "timezone_name", "timezone_offset_hours_utc", "users", "tax_id_status" + }; + + static final String[] AD_ACCOUNT_INSIGHT_FIELDS = { + "account_id", "account_name", "date_start", "date_stop", "actions_per_impression", "clicks", "unique_clicks", + "cost_per_result", "cost_per_total_action", "cpc", "cost_per_unique_click", "cpm", "cpp", "ctr", "unique_ctr", + "frequency", "impressions", "unique_impressions", "objective", "reach", "result_rate", "results", "roas", + "social_clicks", "unique_social_clicks", "social_impressions", "unique_social_impressions", "social_reach", + "spend", "today_spend", "total_action_value", "total_actions", "total_unique_actions", "actions", + "unique_actions", "cost_per_action_type", "video_start_actions" + }; + + /** + * Get all ad accounts for given user. + * + * @param userId the id of an user + * @return the list of {@link AdAccount} objects + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + List getAdAccounts(String userId); + + /** + * Get the ad account by given id. + * + * @param accountId the id of an ad account + * @return the {@link AdAccount} object + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + AdAccount getAdAccount(String accountId); + + /** + * Get all users of the ad account. + * + * @param accountId the id of an ad account + * @return the list of {@link AdUser} objects + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + List getAdAccountUsers(String accountId); + +// problems with Facebook Marketing API +// /** +// * Add the user to the ad account. +// * +// * @param accountId the id of an ad account +// * @param userId the id of an user +// * @param role the role for the new user in ad account +// * @throws ApiException if there is an error while communicating with Facebook. +// * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. +// * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. +// */ +// void addUserToAdAccount(String accountId, String userId, AdUserRole role); +// +// /** +// * Remove user's access to an ad account. +// * +// * @param accountId the id of an ad account +// * @param userId the id of an user +// * @throws ApiException if there is an error while communicating with Facebook. +// * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. +// * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. +// */ +// void deleteUserFromAdAccount(String accountId, String userId); + + /** + * Get the insight for the ad account in aggregate. + * + * @param accountId the id of an ad account. + * @return the {@link AdInsight} object + * @throws ApiException if there is an error while communicating with Facebook. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + */ + AdInsight getAdAccountInsight(String accountId); + + /** + * Updates the ad account name. + * + * @param accountId the id of an ad account. + * @param name the new account name + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void updateAdAccountName(String accountId, String name); + + /** + * Updates the ad account spending cap. + * + * @param accountId the id of an ad account. + * @param spendCap The total amount that account can spend. Value specified in basic unit of the currency, e.g. dollars for USD. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void updateAdAccountSpendCap(String accountId, int spendCap); +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccount.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccount.java new file mode 100644 index 000000000..fb79b8651 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccount.java @@ -0,0 +1,265 @@ +package org.springframework.social.facebook.api; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Model class representing an ad account. + * + * @author Sebastian Górecki + */ +public class AdAccount extends FacebookObject { + private String id; + private List accountGroups; + private long accountId; + private AccountStatus status; + private int age; + private AgencyClientDeclaration agencyClientDeclaration; + private String amountSpent; + private String balance; + private String businessCity; + private String businessCountryCode; + private String businessName; + private String businessState; + private String businessStreet; + private String businessStreet2; + private String businessZip; + private List capabilities; + private Date createdTime; + private String currency; + private String dailySpendLimit; + private long endAdvertiser; + private String fundingSource; + private Map fundingSourceDetails; + private int isPersonal; + private long mediaAgency; + private String name; + private boolean offsitePixelsTOSAccepted; + private long partner; + private String spendCap; + private int timezoneId; + private String timezoneName; + private int timezoneOffsetHoursUTC; + private Map tosAccepted; + private List users; + private TaxStatus taxStatus; + + public String getId() { + return id; + } + + public List getAccountGroups() { + return accountGroups; + } + + public long getAccountId() { + return accountId; + } + + public AccountStatus getStatus() { + return status; + } + + public int getAge() { + return age; + } + + public AgencyClientDeclaration getAgencyClientDeclaration() { + return agencyClientDeclaration; + } + + public String getAmountSpent() { + return amountSpent; + } + + public String getBalance() { + return balance; + } + + public String getBusinessCity() { + return businessCity; + } + + public String getBusinessCountryCode() { + return businessCountryCode; + } + + public String getBusinessName() { + return businessName; + } + + public String getBusinessState() { + return businessState; + } + + public String getBusinessStreet() { + return businessStreet; + } + + public String getBusinessStreet2() { + return businessStreet2; + } + + public String getBusinessZip() { + return businessZip; + } + + public List getCapabilities() { + return capabilities; + } + + public Date getCreatedTime() { + return createdTime; + } + + public String getCurrency() { + return currency; + } + + public String getDailySpendLimit() { + return dailySpendLimit; + } + + public long getEndAdvertiser() { + return endAdvertiser; + } + + public String getFundingSource() { + return fundingSource; + } + + public Map getFundingSourceDetails() { + return fundingSourceDetails; + } + + public int getIsPersonal() { + return isPersonal; + } + + public long getMediaAgency() { + return mediaAgency; + } + + public String getName() { + return name; + } + + public boolean isOffsitePixelsTOSAccepted() { + return offsitePixelsTOSAccepted; + } + + public long getPartner() { + return partner; + } + + public String getSpendCap() { + return spendCap; + } + + public int getTimezoneId() { + return timezoneId; + } + + public String getTimezoneName() { + return timezoneName; + } + + public int getTimezoneOffsetHoursUTC() { + return timezoneOffsetHoursUTC; + } + + public Map getTosAccepted() { + return tosAccepted; + } + + public List getUsers() { + return users; + } + + public TaxStatus getTaxStatus() { + return taxStatus; + } + + public enum AccountStatus { + ACTIVE, DISABLED, UNSETTLED, PENDING_REVIEW, IN_GRACE_PERIOD, TEMPORARILY_UNAVAILABLE, PENDING_CLOSURE, UNKNOWN + } + + public enum Capabilities { + BULK_ACCOUNT, CAN_CREATE_LOOKALIKES_WITH_CUSTOM_RATIO, CAN_USE_CONVERSION_LOOKALIKES, CAN_USE_MOBILE_EXTERNAL_PAGE_TYPE_FOR_LPP, + CAN_USE_REACH_AND_FREQUENCY, CUSTOM_CLUSTER_SHARING, DIRECT_SALES, HAS_AD_SET_TARGETING, HAS_AVAILABLE_PAYMENT_METHODS, + HOLDOUT_VIEW_TAGS, MOBILE_ADVERTISER_ID_UPLOAD, MOBILE_APP_REENGAGEMENT_ADS, MOBILE_APP_VIDEO_ADS, + NEKO_DESKTOP_CANVAS_APP_ADS, NEW_CAMPAIGN_STRUCTURE, PREMIUM, VIEW_TAGS, PRORATED_BUDGET, OFFSITE_CONVERSION_HIGH_BID, + CAN_USE_MOBILE_EXTERNAL_PAGE_TYPE, CAN_USE_OLD_AD_TYPES, CAN_USE_VIDEO_METRICS_BREAKDOWN, ADS_CF_INSTORE_DAILY_BUDGET, + AD_SET_PROMOTED_OBJECT_APP, AD_SET_PROMOTED_OBJECT_OFFER, AD_SET_PROMOTED_OBJECT_PAGE, AD_SET_PROMOTED_OBJECT_PIXEL, + CONNECTIONS_UI_V2, LOOKALIKE_AUDIENCE, CUSTOM_AUDIENCES_OPT_OUT_LINK, CUSTOM_AUDIENCES_FOLDERS, UNKNOWN + } + + public enum TaxStatus { + UNKNOWN, VAT_NOT_REQUIRED_US_CA, VAT_INFORMATION_REQUIRED, VAT_INFORMATION_SUBMITTED, OFFLINE_VAT_VALIDATION_FAILED, + ACCOUNT_IS_PERSONAL_ACCOUNT + } + + public class AgencyClientDeclaration { + private int agencyRepresentingClient; + private int clientBasedInFrance; + private String clientCity; + private String clientCountryCode; + private String clientEmailAddress; + private String clientName; + private String clientPostalCode; + private String clientProvince; + private String clientStreet; + private String clientStreet2; + private int hasWrittenMandateFromAdvertiser; + private int isClientPayingInvoices; + + public int getAgencyRepresentingClient() { + return agencyRepresentingClient; + } + + public int getClientBasedInFrance() { + return clientBasedInFrance; + } + + public String getClientCity() { + return clientCity; + } + + public String getClientCountryCode() { + return clientCountryCode; + } + + public String getClientEmailAddress() { + return clientEmailAddress; + } + + public String getClientName() { + return clientName; + } + + public String getClientPostalCode() { + return clientPostalCode; + } + + public String getClientProvince() { + return clientProvince; + } + + public String getClientStreet() { + return clientStreet; + } + + public String getClientStreet2() { + return clientStreet2; + } + + public int getHasWrittenMandateFromAdvertiser() { + return hasWrittenMandateFromAdvertiser; + } + + public int getIsClientPayingInvoices() { + return isClientPayingInvoices; + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccountGroup.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccountGroup.java new file mode 100644 index 000000000..0a0eaa696 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccountGroup.java @@ -0,0 +1,24 @@ +package org.springframework.social.facebook.api; + +/** + * Model class representing an ad account group. + * + * @author Sebastian Górecki + */ +public class AdAccountGroup extends FacebookObject { + private String id; + private String name; + private int status; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public int getStatus() { + return status; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsight.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsight.java new file mode 100644 index 000000000..e76479088 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsight.java @@ -0,0 +1,252 @@ +package org.springframework.social.facebook.api; + +import java.util.Date; +import java.util.List; + +/** + * Class representing response object given by Ad Insights API for request + * about AdAccount, AdCampaign, AdSet or Ad. + * + * @author Sebastian Górecki + */ +public class AdInsight { + // id fields + private String accountId; + private String adGroupId; + private String campaignId; + private String campaignGroupId; + + // name fields + private String accountName; + private String adGroupName; + private String campaignGroupName; + private String camapignName; + + // date fields + private Date dateStart; + private Date dateStop; + private Date campaignStart; + private Date campaignEnd; + private Date campaignGroupEnd; + + // general fields + private double actionsPerImpression; + private int clicks; + private int uniqueClicks; + private double costPerResult; + private double costPerTotalAction; + private double costPerClick; + private double costPerUniqueClick; + private double cpm; + private double cpp; + private double ctr; + private double uniqueCtr; + private double frequency; + private int impressions; + private int uniqueImpressions; + private String objective; + private int reach; + private double resultRate; + private int results; + private int roas; + private int socialClicks; + private int uniqueSocialClicks; + private int socialImpressions; + private int uniqueSocialImpressions; + private int socialReach; + private int spend; + private int todaySpend; + private int totalActionValue; + private int totalActions; + private int totalUniqueActions; + + // action and video fields + private List actions; + private List uniqueActions; + private List costPerActionType; + private List videoStartActions; + + public String getAccountId() { + return accountId; + } + + public String getAdGroupId() { + return adGroupId; + } + + public String getCampaignId() { + return campaignId; + } + + public String getCampaignGroupId() { + return campaignGroupId; + } + + public String getAccountName() { + return accountName; + } + + public String getAdGroupName() { + return adGroupName; + } + + public String getCampaignGroupName() { + return campaignGroupName; + } + + public String getCamapignName() { + return camapignName; + } + + public Date getDateStart() { + return dateStart; + } + + public Date getDateStop() { + return dateStop; + } + + public Date getCampaignStart() { + return campaignStart; + } + + public Date getCampaignEnd() { + return campaignEnd; + } + + public Date getCampaignGroupEnd() { + return campaignGroupEnd; + } + + public double getActionsPerImpression() { + return actionsPerImpression; + } + + public int getClicks() { + return clicks; + } + + public int getUniqueClicks() { + return uniqueClicks; + } + + public double getCostPerResult() { + return costPerResult; + } + + public double getCostPerTotalAction() { + return costPerTotalAction; + } + + public double getCostPerClick() { + return costPerClick; + } + + public double getCostPerUniqueClick() { + return costPerUniqueClick; + } + + public double getCpm() { + return cpm; + } + + public double getCpp() { + return cpp; + } + + public double getCtr() { + return ctr; + } + + public double getUniqueCtr() { + return uniqueCtr; + } + + public double getFrequency() { + return frequency; + } + + public int getImpressions() { + return impressions; + } + + public int getUniqueImpressions() { + return uniqueImpressions; + } + + public String getObjective() { + return objective; + } + + public int getReach() { + return reach; + } + + public double getResultRate() { + return resultRate; + } + + public int getResults() { + return results; + } + + public int getRoas() { + return roas; + } + + public int getSocialClicks() { + return socialClicks; + } + + public int getUniqueSocialClicks() { + return uniqueSocialClicks; + } + + public int getSocialImpressions() { + return socialImpressions; + } + + public int getUniqueSocialImpressions() { + return uniqueSocialImpressions; + } + + public int getSocialReach() { + return socialReach; + } + + public int getSpend() { + return spend; + } + + public int getTodaySpend() { + return todaySpend; + } + + public int getTotalActionValue() { + return totalActionValue; + } + + public int getTotalActions() { + return totalActions; + } + + public int getTotalUniqueActions() { + return totalUniqueActions; + } + + public List getActions() { + return actions; + } + + public List getUniqueActions() { + return uniqueActions; + } + + public List getCostPerActionType() { + return costPerActionType; + } + + public List getVideoStartActions() { + return videoStartActions; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsightAction.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsightAction.java new file mode 100644 index 000000000..12c68322d --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsightAction.java @@ -0,0 +1,19 @@ +package org.springframework.social.facebook.api; + +/** + * Model class representing an ad insight action. + * + * @author Sebastian Górecki + */ +public class AdInsightAction { + private String actionType; + private double value; + + public String getActionType() { + return actionType; + } + + public double getValue() { + return value; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdUser.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdUser.java new file mode 100644 index 000000000..cc4a8e221 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdUser.java @@ -0,0 +1,39 @@ +package org.springframework.social.facebook.api; + +import java.util.List; + +/** + * Model class representing an ad user. + * + * @author Sebastian Górecki + */ +public class AdUser extends FacebookObject { + private String id; + private String name; + private List permissions; + private AdUserRole role; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public List getPermissions() { + return permissions; + } + + public AdUserRole getRole() { + return role; + } + + public enum AdUserPermission { + ACCOUNT_ADMIN, ADMANAGER_READ, ADMANAGER_WRITE, BILLING_READ, BILLING_WRITE, REPORTS, UNKNOWN + } + + public enum AdUserRole { + ADMINISTRATOR, ADVERTISER, ANALYST, SALES, UNKNOWN + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java new file mode 100644 index 000000000..e6e9fabe9 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java @@ -0,0 +1,13 @@ +package org.springframework.social.facebook.api; + +import org.springframework.social.facebook.api.impl.FacebookAdsTemplate; + +/** + * Interface specifying a basic set of operations for interacting with Facebook Marketing API. + * Implemented by {@link FacebookAdsTemplate}. + * + * @author Sebastian Górecki + */ +public interface FacebookAds { + AccountOperations accountOperations(); +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java new file mode 100644 index 000000000..d04f56a33 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java @@ -0,0 +1,102 @@ +package org.springframework.social.facebook.api.impl; + +import org.springframework.social.facebook.api.*; +import org.springframework.social.facebook.api.AdUser.AdUserRole; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +/** + * @author Sebastian Górecki + */ +public class AccountTemplate extends AbstractFacebookOperations implements AccountOperations { + + private final GraphApi graphApi; + + private final RestTemplate restTemplate; + + public AccountTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean isAuthorizedForUser) { + super(isAuthorizedForUser); + this.graphApi = graphApi; + this.restTemplate = restTemplate; + } + + public List getAdAccounts(String userId) { + requireAuthorization(); + return graphApi.fetchConnections(userId, "adaccounts", AdAccount.class, AD_ACCOUNT_FIELDS); + } + + public AdAccount getAdAccount(String id) { + requireAuthorization(); + return graphApi.fetchObject(id, AdAccount.class, AD_ACCOUNT_FIELDS); + } + + public List getAdAccountUsers(String accountId) { + requireAuthorization(); + return graphApi.fetchConnections(accountId, "users", AdUser.class); + } + +// problem with Facebook Marketing API +// public void addUserToAdAccount(String accountId, String userId, AdUserRole role) { +// requireAuthorization(); +// MultiValueMap map = new LinkedMultiValueMap(); +// map.set("uid", userId); +// map.set("role", String.valueOf(serializeRole(role))); +// graphApi.post(accountId + "/users", "", map); +// } +// +// public void deleteUserFromAdAccount(String accountId, String userId) { +// requireAuthorization(); +// graphApi.delete(accountId + "/users/" + userId); +// } +// + public AdInsight getAdAccountInsight(String accountId) { + requireAuthorization(); + PagedList insights = graphApi.fetchConnections(accountId, "insights", AdInsight.class, AD_ACCOUNT_INSIGHT_FIELDS); + return insights.get(0); + } + + public void updateAdAccountName(String accountId, String name) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.set("name", name); + graphApi.post(accountId, "", map); + } + + public void updateAdAccountSpendCap(String accountId, int spendCap) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.set("spend_cap", String.valueOf(spendCap)); + graphApi.post(accountId, "", map); + } + + private int serializeRole(AdUserRole role) { + switch (role) { + case ADMINISTRATOR: + return 1001; + case ADVERTISER: + return 1002; + case ANALYST: + return 1003; + case SALES: + return 1004; + case UNKNOWN: + default: + return 0; + } + } + + private String join(String[] strings) { + StringBuilder builder = new StringBuilder(); + if (strings.length > 0) { + builder.append(strings[0]); + for (int i = 1; i < strings.length; i++) { + builder.append("," + strings[i]); + } + } + return builder.toString(); + } + +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java new file mode 100644 index 000000000..987d37cb4 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java @@ -0,0 +1,32 @@ +package org.springframework.social.facebook.api.impl; + +import org.springframework.social.facebook.api.AccountOperations; +import org.springframework.social.facebook.api.FacebookAds; + +/** + * This is the central class for interacting with Facebook Marketing API. + * + * @author Sebastian Górecki + */ +public class FacebookAdsTemplate extends FacebookTemplate implements FacebookAds { + + private AccountOperations accountOperations; + + public FacebookAdsTemplate() { + super(null); + initSubApis(); + } + + public FacebookAdsTemplate(String accessToken) { + super(accessToken); + initSubApis(); + } + + public AccountOperations accountOperations() { + return accountOperations; + } + + private void initSubApis() { + accountOperations = new AccountTemplate(this, getRestTemplate(), isAuthorized()); + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountGroupMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountGroupMixin.java new file mode 100644 index 000000000..ea93e974f --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountGroupMixin.java @@ -0,0 +1,21 @@ +package org.springframework.social.facebook.api.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Annotated mixin to add Jackson annotations to AdAccountGroup. + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class AdAccountGroupMixin extends FacebookObjectMixin { + + @JsonProperty("account_group_id") + private String id; + + @JsonProperty("name") + private String name; + + @JsonProperty("status") + private int status; +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountMixin.java new file mode 100644 index 000000000..6b9517608 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountMixin.java @@ -0,0 +1,276 @@ +package org.springframework.social.facebook.api.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.social.facebook.api.AdAccount; +import org.springframework.social.facebook.api.AdAccount.AccountStatus; +import org.springframework.social.facebook.api.AdAccount.AgencyClientDeclaration; +import org.springframework.social.facebook.api.AdAccount.Capabilities; +import org.springframework.social.facebook.api.AdAccount.TaxStatus; +import org.springframework.social.facebook.api.AdAccountGroup; +import org.springframework.social.facebook.api.AdUser; + +import java.io.IOException; +import java.util.*; + +/** + * Annotated mixin to add Jackson annotations to AdAccount. + * + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class AdAccountMixin extends FacebookObjectMixin { + + @JsonProperty("id") + String id; + + @JsonProperty("account_groups") + List accountGroups; + + @JsonProperty("account_id") + long accountId; + + @JsonProperty("account_status") + @JsonDeserialize(using = AccountStatusDeserializer.class) + AccountStatus status; + + @JsonProperty("age") + int age; + + @JsonProperty("agency_client_declaration") + AgencyClientDeclaration agencyClientDeclaration; + + @JsonProperty("amount_spent") + String amountSpent; + + @JsonProperty("balance") + String balance; + + @JsonProperty("business_city") + String businessCity; + + @JsonProperty("business_country_code") + String businessCountryCode; + + @JsonProperty("business_name") + String businessName; + + @JsonProperty("business_state") + String businessState; + + @JsonProperty("business_street") + String businessStreet; + + @JsonProperty("business_street2") + String businessStreet2; + + @JsonProperty("business_zip") + String businessZip; + + @JsonProperty("capabilities") + @JsonDeserialize(using = CapabilitiesListDeserializer.class) + List capabilities; + + @JsonProperty("created_time") + Date createdTime; + + @JsonProperty("currency") + String currency; + + @JsonProperty("daily_spend_limit") + String dailySpendLimit; + + @JsonProperty("end_advertiser") + long endAdvertiser; + + @JsonProperty("funding_source") + String fundingSource; + + @JsonProperty("funding_source_details") + Map fundingSourceDetails; + + @JsonProperty("is_personal") + int isPersonal; + + @JsonProperty("media_agency") + long mediaAgency; + + @JsonProperty("name") + String name; + + @JsonProperty("offsite_pixels_tos_accepted") + boolean offsitePixelsTOSAccepted; + + @JsonProperty("partner") + long partner; + + @JsonProperty("spend_cap") + String spendCap; + + @JsonProperty("timezone_id") + int timezoneId; + + @JsonProperty("timezone_name") + String timezoneName; + + @JsonProperty("timezone_offset_hours_utc") + int timezoneOffsetHoursUTC; + + @JsonProperty("tos_accepted") + Map tosAccepted; + + @JsonProperty("users") + @JsonDeserialize(using = AdUserListDeserializer.class) + List users; + + @JsonProperty("tax_id_status") + @JsonDeserialize(using = TaxStatusDeserializer.class) + TaxStatus taxStatus; + + private static class AccountStatusDeserializer extends JsonDeserializer { + @Override + public AccountStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + int status = jp.getIntValue(); + switch (status) { + case 1: + return AccountStatus.ACTIVE; + case 2: + return AccountStatus.DISABLED; + case 3: + return AccountStatus.UNSETTLED; + case 7: + return AccountStatus.PENDING_REVIEW; + case 9: + return AccountStatus.IN_GRACE_PERIOD; + case 101: + return AccountStatus.TEMPORARILY_UNAVAILABLE; + case 100: + return AccountStatus.PENDING_CLOSURE; + default: + return AccountStatus.UNKNOWN; + } + } catch (IOException e) { + return AccountStatus.UNKNOWN; + } + } + } + + private static class TaxStatusDeserializer extends JsonDeserializer { + @Override + public TaxStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + int status = jp.getIntValue(); + switch (status) { + case 0: + return TaxStatus.UNKNOWN; + case 1: + return TaxStatus.VAT_NOT_REQUIRED_US_CA; + case 2: + return TaxStatus.VAT_INFORMATION_REQUIRED; + case 3: + return TaxStatus.VAT_INFORMATION_SUBMITTED; + case 4: + return TaxStatus.OFFLINE_VAT_VALIDATION_FAILED; + case 5: + return TaxStatus.ACCOUNT_IS_PERSONAL_ACCOUNT; + default: + return TaxStatus.UNKNOWN; + } + } catch (IOException e) { + return TaxStatus.UNKNOWN; + } + } + } + + private static class AdUserListDeserializer extends JsonDeserializer> { + @SuppressWarnings("unchecked") + @Override + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new FacebookModule()); + jp.setCodec(mapper); + if (jp.hasCurrentToken()) { + JsonNode dataNode = jp.readValueAs(JsonNode.class).get("data"); + if (dataNode != null) { + return (List) mapper.reader(new TypeReference>() { + }).readValue(dataNode); + } + } + + return Collections.emptyList(); + } + } + + private static class CapabilitiesListDeserializer extends JsonDeserializer> { + @Override + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + if (jp.getCurrentToken() == JsonToken.START_ARRAY) { + List capabilities = new ArrayList(); + try { + while (jp.nextToken() != JsonToken.END_ARRAY) { + String capability = jp.getValueAsString(); + try { + capabilities.add(Capabilities.valueOf(capability.toUpperCase())); + } catch (IllegalArgumentException e) { + capabilities.add(Capabilities.UNKNOWN); + } + } + return capabilities; + } catch (IOException e) { + return Collections.emptyList(); + } + } + return Collections.emptyList(); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract class AgencyClientDeclarationMixin extends FacebookObjectMixin { + + @JsonProperty("agency_representing_client") + private int agencyRepresentingClient; + + @JsonProperty("client_based_in_france") + private int clientBasedInFrance; + + @JsonProperty("client_city") + private String clientCity; + + @JsonProperty("client_country_code") + private String clientCountryCode; + + @JsonProperty("client_email_address") + private String clientEmailAddress; + + @JsonProperty("client_name") + private String clientName; + + @JsonProperty("client_postal_code") + private String clientPostalCode; + + @JsonProperty("client_province") + private String clientProvince; + + @JsonProperty("client_street") + private String clientStreet; + + @JsonProperty("client_street2") + private String clientStreet2; + + @JsonProperty("has_written_mandate_from_advertiser") + private int hasWrittenMandateFromAdvertiser; + + @JsonProperty("is_client_paying_invoices") + private int isClientPayingInvoices; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightActionMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightActionMixin.java new file mode 100644 index 000000000..1c2fe6ce8 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightActionMixin.java @@ -0,0 +1,16 @@ +package org.springframework.social.facebook.api.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class AdInsightActionMixin extends FacebookObjectMixin { + @JsonProperty("action_type") + private String actionType; + + @JsonProperty("value") + private double value; +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightMixin.java new file mode 100644 index 000000000..75eade6ec --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightMixin.java @@ -0,0 +1,159 @@ +package org.springframework.social.facebook.api.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.social.facebook.api.AdInsightAction; + +import java.util.Date; +import java.util.List; + +/** + * Annotated mixin to add Jackson annotations to AdInsight. + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class AdInsightMixin extends FacebookObjectMixin { + // id fields + @JsonProperty("account_id") + private String accountId; + + @JsonProperty("adgroup_id") + private String adGroupId; + + @JsonProperty("campaign_id") + private String campaignId; + + @JsonProperty("campaign_group_id") + private String campaignGroupId; + + // name fields + @JsonProperty("account_name") + private String accountName; + + @JsonProperty("adgroup_name") + private String adGroupName; + + @JsonProperty("campaign_group_name") + private String campaignGroupName; + + @JsonProperty("campaign_name") + private String camapignName; + + // date fields + @JsonProperty("date_start") + private Date dateStart; + + @JsonProperty("date_stop") + private Date dateStop; + + @JsonProperty("campaign_start") + private Date campaignStart; + + @JsonProperty("campaign_end") + private Date campaignEnd; + + @JsonProperty("campaign_group_end") + private Date campaignGroupEnd; + + // general fields + @JsonProperty("actions_per_impression") + private double actionsPerImpression; + + @JsonProperty("clicks") + private int clicks; + + @JsonProperty("unique_clicks") + private int uniqueClicks; + + @JsonProperty("cost_per_result") + private double costPerResult; + + @JsonProperty("cost_per_total_action") + private double costPerTotalAction; + + @JsonProperty("cpc") + private double costPerClick; + + @JsonProperty("cost_per_unique_click") + private double costPerUniqueClick; + + @JsonProperty("cpm") + private double cpm; + + @JsonProperty("cpp") + private double cpp; + + @JsonProperty("ctr") + private double ctr; + + @JsonProperty("unique_ctr") + private double uniqueCtr; + + @JsonProperty("frequency") + private double frequency; + + @JsonProperty("impressions") + private int impressions; + + @JsonProperty("unique_impressions") + private int uniqueImpressions; + + @JsonProperty("objective") + private String objective; + + @JsonProperty("reach") + private int reach; + + @JsonProperty("result_rate") + private double resultRate; + + @JsonProperty("results") + private int results; + + @JsonProperty("roas") + private int roas; + + @JsonProperty("social_clicks") + private int socialClicks; + + @JsonProperty("unique_social_clicks") + private int uniqueSocialClicks; + + @JsonProperty("social_impressions") + private int socialImpressions; + + @JsonProperty("unique_social_impressions") + private int uniqueSocialImpressions; + + @JsonProperty("social_reach") + private int socialReach; + + @JsonProperty("spend") + private int spend; + + @JsonProperty("today_spend") + private int todaySpend; + + @JsonProperty("total_action_value") + private int totalActionValue; + + @JsonProperty("total_actions") + private int totalActions; + + @JsonProperty("total_unique_actions") + private int totalUniqueActions; + + // action and video fields + @JsonProperty("actions") + private List actions; + + @JsonProperty("unique_actions") + private List uniqueActions; + + @JsonProperty("cost_per_action_type") + private List costPerActionType; + + @JsonProperty("video_start_actions") + private List videoStartActions; + +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdUserMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdUserMixin.java new file mode 100644 index 000000000..14c5f9a3b --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdUserMixin.java @@ -0,0 +1,103 @@ +package org.springframework.social.facebook.api.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.social.facebook.api.AdUser.AdUserPermission; +import org.springframework.social.facebook.api.AdUser.AdUserRole; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Annotated mixin to add Jackson annotations to AdUser. + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class AdUserMixin extends FacebookObjectMixin { + @JsonProperty("id") + private String id; + + @JsonProperty("name") + private String name; + + @JsonProperty("permissions") + @JsonDeserialize(using = AdUserPermissionListDeserializer.class) + private List permissions; + + @JsonProperty("role") + @JsonDeserialize(using = AdUserRoleDeserializer.class) + private AdUserRole role; + + private static class AdUserRoleDeserializer extends JsonDeserializer { + @Override + public AdUserRole deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + int status = jp.getIntValue(); + switch (status) { + case 1001: + return AdUserRole.ADMINISTRATOR; + case 1002: + return AdUserRole.ADVERTISER; + case 1003: + return AdUserRole.ANALYST; + case 1004: + return AdUserRole.SALES; + default: + return AdUserRole.UNKNOWN; + } + } catch (IOException e) { + return AdUserRole.UNKNOWN; + } + } + } + + private static class AdUserPermissionListDeserializer extends JsonDeserializer> { + @Override + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + if (jp.getCurrentToken() == JsonToken.START_ARRAY) { + List permissions = new ArrayList(); + try { + while (jp.nextToken() != JsonToken.END_ARRAY) { + int permissionValue = jp.getIntValue(); + switch (permissionValue) { + case 1: + permissions.add(AdUserPermission.ACCOUNT_ADMIN); + break; + case 2: + permissions.add(AdUserPermission.ADMANAGER_READ); + break; + case 3: + permissions.add(AdUserPermission.ADMANAGER_WRITE); + break; + case 4: + permissions.add(AdUserPermission.BILLING_READ); + break; + case 5: + permissions.add(AdUserPermission.BILLING_WRITE); + break; + case 7: + permissions.add(AdUserPermission.REPORTS); + break; + default: + permissions.add(AdUserPermission.UNKNOWN); + break; + } + } + return permissions; + } catch (IOException e) { + return Collections.emptyList(); + } + } + return Collections.emptyList(); + } + } + +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java index ade1c0078..5f9b65e14 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java @@ -19,6 +19,11 @@ import org.springframework.social.facebook.api.Achievement; import org.springframework.social.facebook.api.AchievementType; import org.springframework.social.facebook.api.Action; +import org.springframework.social.facebook.api.AdAccount; +import org.springframework.social.facebook.api.AdAccountGroup; +import org.springframework.social.facebook.api.AdInsight; +import org.springframework.social.facebook.api.AdInsightAction; +import org.springframework.social.facebook.api.AdUser; import org.springframework.social.facebook.api.Album; import org.springframework.social.facebook.api.ApplicationReference; import org.springframework.social.facebook.api.Comment; @@ -170,6 +175,16 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(VideoUploadLimits.class, VideoUploadLimitsMixin.class); context.setMixInAnnotations(ProfilePictureSource.class, ProfilePictureSourceMixin.class); - + + + context.setMixInAnnotations(AdAccountGroup.class, AdAccountGroupMixin.class); + context.setMixInAnnotations(AdAccount.AgencyClientDeclaration.class, AdAccountMixin.AgencyClientDeclarationMixin.class); + context.setMixInAnnotations(AdUser.class, AdUserMixin.class); + + context.setMixInAnnotations(AdInsightAction.class, AdInsightActionMixin.class); + context.setMixInAnnotations(AdInsight.class, AdInsightMixin.class); + + context.setMixInAnnotations(AdAccount.class, AdAccountMixin.class); + } } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AbstractFacebookAdsApiTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AbstractFacebookAdsApiTest.java new file mode 100644 index 000000000..e41c2d211 --- /dev/null +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AbstractFacebookAdsApiTest.java @@ -0,0 +1,47 @@ +package org.springframework.social.facebook.api; + +import org.junit.Before; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.social.facebook.api.impl.FacebookAdsTemplate; +import org.springframework.test.web.client.MockRestServiceServer; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * @author Sebastian Górecki + */ +public class AbstractFacebookAdsApiTest { + protected static final String ACCESS_TOKEN = "someAccessToken"; + private static final DateFormat FB_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ENGLISH); + protected FacebookAdsTemplate facebookAds; + protected FacebookAdsTemplate unauthorizedFacebookAds; + protected MockRestServiceServer mockServer; + protected MockRestServiceServer unauthorizedMockServer; + + @Before + public void setUp() throws Exception { + facebookAds = new FacebookAdsTemplate(ACCESS_TOKEN); + mockServer = MockRestServiceServer.createServer(facebookAds.getRestTemplate()); + + unauthorizedFacebookAds = new FacebookAdsTemplate(); + unauthorizedMockServer = MockRestServiceServer.createServer(unauthorizedFacebookAds.getRestTemplate()); + } + + protected Resource jsonResource(String filename) { + return new ClassPathResource(filename + ".json", getClass()); + } + + protected Date toDate(String dateString) { + try { + return FB_DATE_FORMAT.parse(dateString); + } catch (ParseException e) { + return null; + } + } + +} diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java new file mode 100644 index 000000000..06a438739 --- /dev/null +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java @@ -0,0 +1,469 @@ +package org.springframework.social.facebook.api; + +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.social.NotAuthorizedException; +import org.springframework.social.facebook.api.AdAccount.Capabilities; +import org.springframework.social.facebook.api.AdAccount.TaxStatus; +import org.springframework.social.facebook.api.AdUser.AdUserPermission; +import org.springframework.social.facebook.api.AdUser.AdUserRole; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * @author Sebastian Górecki + */ +public class AccountTemplateTest extends AbstractFacebookAdsApiTest { + + private static final String GET_ADACCOUNT_REQUEST_URI = "https://graph.facebook.com/v2.3/act_123456789?fields=id%2Caccount_id%2Caccount_status%2Cage%2Camount_spent%2Cbalance%2Cbusiness_city%2Cbusiness_country_code%2Cbusiness_name%2Cbusiness_state%2Cbusiness_street%2Cbusiness_street2%2Cbusiness_zip%2Ccapabilities%2Ccreated_time%2Ccurrency%2Cdaily_spend_limit%2Cend_advertiser%2Cfunding_source%2Cfunding_source_details%2Cis_personal%2Cmedia_agency%2Cname%2Coffsite_pixels_tos_accepted%2Cpartner%2Cspend_cap%2Ctimezone_id%2Ctimezone_name%2Ctimezone_offset_hours_utc%2Cusers%2Ctax_id_status"; + private static final String GET_ADACCOUNTS_REQUEST_URI = "https://graph.facebook.com/v2.3/1234/adaccounts?fields=id%2Caccount_id%2Caccount_status%2Cage%2Camount_spent%2Cbalance%2Cbusiness_city%2Cbusiness_country_code%2Cbusiness_name%2Cbusiness_state%2Cbusiness_street%2Cbusiness_street2%2Cbusiness_zip%2Ccapabilities%2Ccreated_time%2Ccurrency%2Cdaily_spend_limit%2Cend_advertiser%2Cfunding_source%2Cfunding_source_details%2Cis_personal%2Cmedia_agency%2Cname%2Coffsite_pixels_tos_accepted%2Cpartner%2Cspend_cap%2Ctimezone_id%2Ctimezone_name%2Ctimezone_offset_hours_utc%2Cusers%2Ctax_id_status"; + private static final String GET_ADACCOUNT_USERS_REQUEST_URI = "https://graph.facebook.com/v2.3/act_123456789/users"; + private static final String GET_ADACCOUNT_INSIGHT = "https://graph.facebook.com/v2.3/act_123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions"; + private static final double EPSILON = 0.000000000001; + + + @Test + public void getAccounts() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNTS_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-accounts"), MediaType.APPLICATION_JSON)); + + List adAccounts = facebookAds.accountOperations().getAdAccounts("1234"); + assertEquals(2, adAccounts.size()); + assertAdAccountsFields(adAccounts); + } + + @Test(expected = NotAuthorizedException.class) + public void getAccounts_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().getAdAccounts("1234"); + } + + @Test + public void getAdAccount() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertEquals(AdAccount.AccountStatus.ACTIVE, adAccount.getStatus()); + assertEquals(1, adAccount.getUsers().size()); + assertEquals("1234", adAccount.getUsers().get(0).getId()); + assertEquals(AdUserPermission.ACCOUNT_ADMIN, adAccount.getUsers().get(0).getPermissions().get(0)); + assertEquals(AdUserPermission.ADMANAGER_READ, adAccount.getUsers().get(0).getPermissions().get(1)); + assertEquals(AdUserPermission.ADMANAGER_WRITE, adAccount.getUsers().get(0).getPermissions().get(2)); + assertEquals(AdUserRole.ADMINISTRATOR, adAccount.getUsers().get(0).getRole()); + } + + @Test + public void getAdAccount_withStatusTemporarilyUnavailable() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-temporarily-unavailable"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertEquals(AdAccount.AccountStatus.TEMPORARILY_UNAVAILABLE, adAccount.getStatus()); + assertEquals(1, adAccount.getUsers().size()); + assertEquals("1234", adAccount.getUsers().get(0).getId()); + assertEquals(AdUserPermission.ACCOUNT_ADMIN, adAccount.getUsers().get(0).getPermissions().get(0)); + assertEquals(AdUserPermission.ADMANAGER_READ, adAccount.getUsers().get(0).getPermissions().get(1)); + assertEquals(AdUserPermission.ADMANAGER_WRITE, adAccount.getUsers().get(0).getPermissions().get(2)); + assertEquals(AdUserRole.ADMINISTRATOR, adAccount.getUsers().get(0).getRole()); + } + + @Test + public void getAdAccount_withUnknownCapabilities() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-unknown-capabilities"), MediaType.APPLICATION_JSON)); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertEquals("act_123456789", adAccount.getId()); + assertEquals(123456789, adAccount.getAccountId()); + assertEquals(4, adAccount.getCapabilities().size()); + assertEquals(Capabilities.UNKNOWN, adAccount.getCapabilities().get(0)); + assertEquals(Capabilities.UNKNOWN, adAccount.getCapabilities().get(1)); + assertEquals(Capabilities.UNKNOWN, adAccount.getCapabilities().get(2)); + assertEquals(Capabilities.PREMIUM, adAccount.getCapabilities().get(3)); + } + + @Test + public void getAdAccount_withEmptyUsers() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-no-users"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertEquals(0, adAccount.getUsers().size()); + } + + @Test + public void getAdAccount_withFewUsers() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-with-few-users"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertEquals(2, adAccount.getUsers().size()); + assertEquals("1234", adAccount.getUsers().get(0).getId()); + assertEquals(AdUserPermission.ACCOUNT_ADMIN, adAccount.getUsers().get(0).getPermissions().get(0)); + assertEquals(AdUserPermission.ADMANAGER_READ, adAccount.getUsers().get(0).getPermissions().get(1)); + assertEquals(AdUserPermission.ADMANAGER_WRITE, adAccount.getUsers().get(0).getPermissions().get(2)); + assertEquals(AdUserRole.ADMINISTRATOR, adAccount.getUsers().get(0).getRole()); + assertEquals("3421", adAccount.getUsers().get(1).getId()); + assertEquals(AdUserPermission.BILLING_WRITE, adAccount.getUsers().get(1).getPermissions().get(0)); + assertEquals(AdUserPermission.REPORTS, adAccount.getUsers().get(1).getPermissions().get(1)); + assertEquals(AdUserRole.ANALYST, adAccount.getUsers().get(1).getRole()); + } + + @Test + public void getAdAccount_userWithNoPermissions() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-without-permission"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertEquals(1, adAccount.getUsers().size()); + assertEquals("1234", adAccount.getUsers().get(0).getId()); + assertEquals(0, adAccount.getUsers().get(0).getPermissions().size()); + assertEquals(AdUserRole.ADMINISTRATOR, adAccount.getUsers().get(0).getRole()); + } + + @Test + public void getAdAccount_withAgencyClientDeclaration() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-with-agency"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertEquals(1, adAccount.getAgencyClientDeclaration().getAgencyRepresentingClient()); + assertEquals(0, adAccount.getAgencyClientDeclaration().getClientBasedInFrance()); + assertEquals("Warsaw", adAccount.getAgencyClientDeclaration().getClientCity()); + assertEquals("PL", adAccount.getAgencyClientDeclaration().getClientCountryCode()); + assertEquals("example@example.com", adAccount.getAgencyClientDeclaration().getClientEmailAddress()); + assertEquals("Some client", adAccount.getAgencyClientDeclaration().getClientName()); + assertEquals("66-777", adAccount.getAgencyClientDeclaration().getClientPostalCode()); + assertEquals("malopolska", adAccount.getAgencyClientDeclaration().getClientProvince()); + assertEquals("Marszalkowska", adAccount.getAgencyClientDeclaration().getClientStreet()); + assertEquals("1A", adAccount.getAgencyClientDeclaration().getClientStreet2()); + assertEquals(0, adAccount.getAgencyClientDeclaration().getHasWrittenMandateFromAdvertiser()); + assertEquals(1, adAccount.getAgencyClientDeclaration().getIsClientPayingInvoices()); + } + + @Test + public void getAdAccount_withFundingDetails() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-with-funding-details"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertTrue(adAccount.getFundingSourceDetails().containsKey("id")); + assertEquals("12345678987654321", adAccount.getFundingSourceDetails().get("id")); + assertTrue(adAccount.getFundingSourceDetails().containsKey("display_string")); + assertEquals("Visa *0001", adAccount.getFundingSourceDetails().get("display_string")); + assertTrue(adAccount.getFundingSourceDetails().containsKey("type")); + assertEquals(1, adAccount.getFundingSourceDetails().get("type")); + } + + @Test + public void getAdAccount_withTosAccepted() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-with-tos-accepted"), MediaType.APPLICATION_JSON)); + + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + assertAdAccountFields(adAccount); + assertTrue(adAccount.getTosAccepted().containsKey("206760949512025")); + assertEquals((Integer) 1, adAccount.getTosAccepted().get("206760949512025")); + assertTrue(adAccount.getTosAccepted().containsKey("215449065224656")); + assertEquals((Integer) 1, adAccount.getTosAccepted().get("215449065224656")); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdAccount_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().getAdAccount("act_123456789"); + } + + @Test + public void getAccountUsers() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_USERS_REQUEST_URI)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-users"), MediaType.APPLICATION_JSON)); + + List adAccountUsers = facebookAds.accountOperations().getAdAccountUsers("act_123456789"); + assertEquals(3, adAccountUsers.size()); + assertEquals("123456789", adAccountUsers.get(0).getId()); + assertEquals("Account #1", adAccountUsers.get(0).getName()); + assertEquals(6, adAccountUsers.get(0).getPermissions().size()); + assertEquals(AdUserPermission.ACCOUNT_ADMIN, adAccountUsers.get(0).getPermissions().get(0)); + assertEquals(AdUserPermission.ADMANAGER_READ, adAccountUsers.get(0).getPermissions().get(1)); + assertEquals(AdUserPermission.ADMANAGER_WRITE, adAccountUsers.get(0).getPermissions().get(2)); + assertEquals(AdUserPermission.BILLING_READ, adAccountUsers.get(0).getPermissions().get(3)); + assertEquals(AdUserPermission.BILLING_WRITE, adAccountUsers.get(0).getPermissions().get(4)); + assertEquals(AdUserPermission.REPORTS, adAccountUsers.get(0).getPermissions().get(5)); + assertEquals(AdUserRole.ADMINISTRATOR, adAccountUsers.get(0).getRole()); + assertEquals("987654321", adAccountUsers.get(1).getId()); + assertEquals("Account #2", adAccountUsers.get(1).getName()); + assertEquals(1, adAccountUsers.get(1).getPermissions().size()); + assertEquals(AdUserPermission.REPORTS, adAccountUsers.get(1).getPermissions().get(0)); + assertEquals(AdUserRole.ANALYST, adAccountUsers.get(1).getRole()); + assertEquals("1122334455", adAccountUsers.get(2).getId()); + assertEquals("Account #3", adAccountUsers.get(2).getName()); + assertEquals(2, adAccountUsers.get(2).getPermissions().size()); + assertEquals(AdUserPermission.ADMANAGER_READ, adAccountUsers.get(2).getPermissions().get(0)); + assertEquals(AdUserPermission.BILLING_READ, adAccountUsers.get(2).getPermissions().get(1)); + assertEquals(AdUserRole.SALES, adAccountUsers.get(2).getRole()); + } + + @Test(expected = NotAuthorizedException.class) + public void getAccountUsers_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().getAdAccountUsers("act_123456789"); + } + +// @Test +// public void addUserToAccount() throws Exception { +// String requestBody = "uid=123456&role=1002"; +// mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/users/")) +// .andExpect(method(POST)) +// .andExpect(content().string(requestBody)) +// .andExpect(header("Authorization", "OAuth someAccessToken")) +// .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); +// facebookAds.accountOperations().addUserToAdAccount("act_123456789", "123456", AdUserRole.ADVERTISER); +// mockServer.verify(); +// } +// +// @Test(expected = NotAuthorizedException.class) +// public void addUserToAccount_unauthorized() throws Exception { +// unauthorizedFacebookAds.accountOperations().addUserToAdAccount("act_123456789", "123456", AdUserRole.ADVERTISER); +// } +// +// @Test +// public void deleteUserFromAccount() throws Exception { +// String requestBody = "method=delete"; +// mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/users/123456")) +// .andExpect(method(POST)) +// .andExpect(header("Authorization", "OAuth someAccessToken")) +// .andExpect(content().string(requestBody)) +// .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); +// facebookAds.accountOperations().deleteUserFromAdAccount("act_123456789", "123456"); +// mockServer.verify(); +// } +// +// @Test(expected = NotAuthorizedException.class) +// public void deleteUserFromAccount_unauthorized() throws Exception { +// unauthorizedFacebookAds.accountOperations().deleteUserFromAdAccount("act_123456789", "123456"); +// } + + @Test + public void getAccountInsight() throws Exception { + mockServer.expect(requestTo(GET_ADACCOUNT_INSIGHT)) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-insights"), MediaType.APPLICATION_JSON)); + + AdInsight insight = facebookAds.accountOperations().getAdAccountInsight("act_123456789"); + assertEquals("123456789", insight.getAccountId()); + assertEquals("Account Test Name #1", insight.getAccountName()); + assertEquals(0.016042780748663, insight.getActionsPerImpression(), EPSILON); + assertEquals(8, insight.getClicks()); + assertEquals(5, insight.getUniqueClicks()); + assertEquals(0.66666666666667, insight.getCostPerResult(), EPSILON); + assertEquals(0.66666666666667, insight.getCostPerTotalAction(), EPSILON); + assertEquals(0.25, insight.getCostPerClick(), EPSILON); + assertEquals(0.4, insight.getCostPerUniqueClick(), EPSILON); + assertEquals(10.695187165775, insight.getCpm(), EPSILON); + assertEquals(10.869565217391, insight.getCpp(), EPSILON); + assertEquals(4.2780748663102, insight.getCtr(), EPSILON); + assertEquals(2.7173913043478, insight.getUniqueCtr(), EPSILON); + assertEquals(1.0163043478261, insight.getFrequency(), EPSILON); + assertEquals(187, insight.getImpressions()); + assertEquals(184, insight.getUniqueImpressions()); + assertEquals(184, insight.getReach()); + assertEquals(1.6042780748663, insight.getResultRate(), EPSILON); + assertEquals(3, insight.getResults()); + assertEquals(1, insight.getRoas()); + assertEquals(2, insight.getSocialClicks()); + assertEquals(3, insight.getUniqueSocialClicks()); + assertEquals(4, insight.getSocialImpressions()); + assertEquals(5, insight.getUniqueSocialImpressions()); + assertEquals(6, insight.getSocialReach()); + assertEquals(2, insight.getSpend()); + assertEquals(0, insight.getTodaySpend()); + assertEquals(0, insight.getTotalActionValue()); + assertEquals(3, insight.getTotalActions()); + assertEquals(2, insight.getTotalUniqueActions()); + assertEquals(4, insight.getActions().size()); + assertEquals("comment", insight.getActions().get(0).getActionType()); + assertEquals(2, insight.getActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getActions().get(1).getActionType()); + assertEquals(1, insight.getActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getActions().get(2).getActionType()); + assertEquals(3, insight.getActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getActions().get(3).getActionType()); + assertEquals(3, insight.getActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getUniqueActions().size()); + assertEquals("comment", insight.getUniqueActions().get(0).getActionType()); + assertEquals(1, insight.getUniqueActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getUniqueActions().get(1).getActionType()); + assertEquals(1, insight.getUniqueActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getUniqueActions().get(2).getActionType()); + assertEquals(2, insight.getUniqueActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getUniqueActions().get(3).getActionType()); + assertEquals(2, insight.getUniqueActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getCostPerActionType().size()); + assertEquals("comment", insight.getCostPerActionType().get(0).getActionType()); + assertEquals(1, insight.getCostPerActionType().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getCostPerActionType().get(1).getActionType()); + assertEquals(2, insight.getCostPerActionType().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getCostPerActionType().get(2).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getCostPerActionType().get(3).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(3).getValue(), EPSILON); + assertEquals(1, insight.getVideoStartActions().size()); + assertEquals("video_view", insight.getVideoStartActions().get(0).getActionType()); + assertEquals(0, insight.getVideoStartActions().get(0).getValue(), EPSILON); + } + + @Test(expected = NotAuthorizedException.class) + public void getAccountInsight_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().getAdAccountInsight("act_123456789"); + } + + @Test + public void updateAccountName() throws Exception { + String requestBody = "name=New+account+name"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.accountOperations().updateAdAccountName("act_123456789", "New account name"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAccountName_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().updateAdAccountName("act_123456789", "New account name"); + } + + @Test + public void updateAccountSpendCap() throws Exception { + String requestBody = "spend_cap=100"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.accountOperations().updateAdAccountSpendCap("act_123456789", 100); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAccountSpendCap_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().updateAdAccountSpendCap("act_123456789", 100); + } + + private void assertAdAccountsFields(List adAccounts) { + assertAdAccountFields(adAccounts.get(0)); + assertAdAccountFields(adAccounts.get(0)); + assertEquals("act_77777777", adAccounts.get(1).getId()); + assertEquals(2, adAccounts.get(1).getAccountGroups().size()); + assertEquals("987654321", adAccounts.get(1).getAccountGroups().get(0).getId()); + assertEquals("Test group name", adAccounts.get(1).getAccountGroups().get(0).getName()); + assertEquals(1, adAccounts.get(1).getAccountGroups().get(0).getStatus()); + assertEquals("11223344", adAccounts.get(1).getAccountGroups().get(1).getId()); + assertEquals("Test group name 2", adAccounts.get(1).getAccountGroups().get(1).getName()); + assertEquals(1, adAccounts.get(1).getAccountGroups().get(1).getStatus()); + assertEquals(77777777, adAccounts.get(1).getAccountId()); + assertEquals(AdAccount.AccountStatus.DISABLED, adAccounts.get(1).getStatus()); + assertEquals(777, adAccounts.get(1).getAge()); + assertEquals("7777", adAccounts.get(1).getAmountSpent()); + assertEquals("77777", adAccounts.get(1).getBalance()); + assertEquals("Warsaw", adAccounts.get(1).getBusinessCity()); + assertEquals("PL", adAccounts.get(1).getBusinessCountryCode()); + assertEquals("Some business name for the account 2", adAccounts.get(1).getBusinessName()); + assertEquals("mazowieckie", adAccounts.get(1).getBusinessState()); + assertEquals("Some street 2", adAccounts.get(1).getBusinessStreet()); + assertEquals(null, adAccounts.get(1).getBusinessStreet2()); + assertEquals("77-777", adAccounts.get(1).getBusinessZip()); + assertEquals(2, adAccounts.get(1).getCapabilities().size()); + assertEquals(Capabilities.DIRECT_SALES, adAccounts.get(1).getCapabilities().get(0)); + assertEquals(Capabilities.VIEW_TAGS, adAccounts.get(1).getCapabilities().get(1)); + assertEquals(toDate("2015-04-20T00:31:33+0100"), adAccounts.get(1).getCreatedTime()); + assertEquals("PLN", adAccounts.get(1).getCurrency()); + assertEquals("77", adAccounts.get(1).getDailySpendLimit()); + assertEquals(987654321, adAccounts.get(1).getEndAdvertiser()); + assertEquals("77", adAccounts.get(1).getFundingSource()); + assertEquals(1, adAccounts.get(1).getIsPersonal()); + assertEquals(54321, adAccounts.get(1).getMediaAgency()); + assertEquals("This is a test account 2", adAccounts.get(1).getName()); + assertEquals(false, adAccounts.get(1).isOffsitePixelsTOSAccepted()); + assertEquals(111222333, adAccounts.get(1).getPartner()); + assertEquals("0", adAccounts.get(1).getSpendCap()); + assertEquals(106, adAccounts.get(1).getTimezoneId()); + assertEquals("Europe/Warsaw", adAccounts.get(1).getTimezoneName()); + assertEquals(2, adAccounts.get(1).getTimezoneOffsetHoursUTC()); + assertEquals(TaxStatus.ACCOUNT_IS_PERSONAL_ACCOUNT, adAccounts.get(1).getTaxStatus()); + } + + + private void assertAdAccountFields(AdAccount adAccount) { + assertEquals("act_123456789", adAccount.getId()); + assertEquals(1, adAccount.getAccountGroups().size()); + assertEquals("987654321", adAccount.getAccountGroups().get(0).getId()); + assertEquals("Test group name", adAccount.getAccountGroups().get(0).getName()); + assertEquals(1, adAccount.getAccountGroups().get(0).getStatus()); + assertEquals(123456789, adAccount.getAccountId()); + assertEquals(10, adAccount.getAge()); + assertEquals("1234", adAccount.getAmountSpent()); + assertEquals("10000", adAccount.getBalance()); + assertEquals("Poznan", adAccount.getBusinessCity()); + assertEquals("PL", adAccount.getBusinessCountryCode()); + assertEquals("Some business name for the account", adAccount.getBusinessName()); + assertEquals("wielkopolska", adAccount.getBusinessState()); + assertEquals("Some street 79A", adAccount.getBusinessStreet()); + assertEquals(null, adAccount.getBusinessStreet2()); + assertEquals("66-777", adAccount.getBusinessZip()); + assertEquals(2, adAccount.getCapabilities().size()); + assertEquals(Capabilities.DIRECT_SALES, adAccount.getCapabilities().get(0)); + assertEquals(Capabilities.VIEW_TAGS, adAccount.getCapabilities().get(1)); + assertEquals(toDate("2015-02-19T00:31:33+0100"), adAccount.getCreatedTime()); + assertEquals("PLN", adAccount.getCurrency()); + assertEquals("93263", adAccount.getDailySpendLimit()); + assertEquals(987654321, adAccount.getEndAdvertiser()); + assertEquals("1122334455", adAccount.getFundingSource()); + assertEquals(1, adAccount.getIsPersonal()); + assertEquals(54321, adAccount.getMediaAgency()); + assertEquals("This is a test account", adAccount.getName()); + assertEquals(false, adAccount.isOffsitePixelsTOSAccepted()); + assertEquals(111222333, adAccount.getPartner()); + assertEquals("0", adAccount.getSpendCap()); + assertEquals(106, adAccount.getTimezoneId()); + assertEquals("Europe/Warsaw", adAccount.getTimezoneName()); + assertEquals(2, adAccount.getTimezoneOffsetHoursUTC()); + assertEquals(TaxStatus.VAT_INFORMATION_SUBMITTED, adAccount.getTaxStatus()); + } +} diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-insights.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-insights.json new file mode 100644 index 000000000..a1af55448 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-insights.json @@ -0,0 +1,106 @@ +{ + "data": [ + { + "account_id": "123456789", + "account_name": "Account Test Name #1", + "date_start": "2015-02-19", + "date_stop": "2015-04-23", + "actions_per_impression": 0.016042780748663, + "clicks": 8, + "unique_clicks": 5, + "cost_per_result": 0.66666666666667, + "cost_per_total_action": 0.66666666666667, + "cpc": 0.25, + "cost_per_unique_click": 0.4, + "cpm": 10.695187165775, + "cpp": 10.869565217391, + "ctr": 4.2780748663102, + "unique_ctr": 2.7173913043478, + "frequency": 1.0163043478261, + "impressions": "187", + "unique_impressions": 184, + "reach": 184, + "result_rate": 1.6042780748663, + "results": 3, + "roas": 1, + "social_clicks": 2, + "unique_social_clicks": 3, + "social_impressions": 4, + "unique_social_impressions": 5, + "social_reach": 6, + "spend": 2, + "today_spend": 0, + "total_action_value": 0, + "total_actions": 3, + "total_unique_actions": 2, + "actions": [ + { + "action_type": "comment", + "value": 2 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 3 + }, + { + "action_type": "post_engagement", + "value": 3 + } + ], + "unique_actions": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 2 + }, + { + "action_type": "post_engagement", + "value": 2 + } + ], + "cost_per_action_type": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 2 + }, + { + "action_type": "page_engagement", + "value": 0.66666666666667 + }, + { + "action_type": "post_engagement", + "value": 0.66666666666667 + } + ], + "video_start_actions": [ + { + "action_type": "video_view", + "value": 0 + } + ], + "date_start": "2015-02-19", + "date_stop": "2015-04-21" + } + ], + "paging": { + "cursors": { + "before": "MA==", + "after": "MA==" + } + } +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-no-users.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-no-users.json new file mode 100644 index 000000000..af48c322b --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-no-users.json @@ -0,0 +1,44 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-temporarily-unavailable.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-temporarily-unavailable.json new file mode 100644 index 000000000..9858ecc61 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-temporarily-unavailable.json @@ -0,0 +1,53 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 101, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + } + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-unknown-capabilities.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-unknown-capabilities.json new file mode 100644 index 000000000..72057e89f --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-unknown-capabilities.json @@ -0,0 +1,10 @@ +{ + "capabilities": [ + "UNKNOWN_CAPABILITY1", + "UNKNOWN_CAPABILITY2", + "UNKNOWN_CAPABILITY3", + "PREMIUM" + ], + "account_id": 123456789, + "id": "act_123456789" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-users.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-users.json new file mode 100644 index 000000000..7fe5875f6 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-users.json @@ -0,0 +1,34 @@ +{ + "data": [ + { + "id": "123456789", + "name": "Account #1", + "permissions": [ + 1, + 2, + 3, + 4, + 5, + 7 + ], + "role": 1001 + }, + { + "id": "987654321", + "name": "Account #2", + "permissions": [ + 7 + ], + "role": 1003 + }, + { + "id": "1122334455", + "name": "Account #3", + "permissions": [ + 2, + 4 + ], + "role": 1004 + } + ] +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-agency.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-agency.json new file mode 100644 index 000000000..d44c3db26 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-agency.json @@ -0,0 +1,67 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "agency_client_declaration": { + "agency_representing_client": 1, + "client_based_in_france": 0, + "client_city": "Warsaw", + "client_country_code": "PL", + "client_email_address": "example@example.com", + "client_name": "Some client", + "client_postal_code": "66-777", + "client_province": "malopolska", + "client_street": "Marszalkowska", + "client_street2": "1A", + "has_written_mandate_from_advertiser": 0, + "is_client_paying_invoices": 1 + }, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + } + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-few-users.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-few-users.json new file mode 100644 index 000000000..0a4fea6dd --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-few-users.json @@ -0,0 +1,61 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + }, + { + "id": "3421", + "permissions": [ + 5, + 7 + ], + "role": 1003 + } + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-funding-details.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-funding-details.json new file mode 100644 index 000000000..627697608 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-funding-details.json @@ -0,0 +1,58 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "funding_source_details": { + "id": "12345678987654321", + "display_string": "Visa *0001", + "type": 1 + }, + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + } + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-tos-accepted.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-tos-accepted.json new file mode 100644 index 000000000..742a628a9 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-tos-accepted.json @@ -0,0 +1,57 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "tos_accepted": { + "206760949512025": 1, + "215449065224656": 1 + }, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + } + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-without-permission.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-without-permission.json new file mode 100644 index 000000000..dae3477ba --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-without-permission.json @@ -0,0 +1,50 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + ], + "role": 1001 + } + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account.json new file mode 100644 index 000000000..1dfeb8673 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account.json @@ -0,0 +1,53 @@ +{ + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + } + ] + }, + "tax_id_status": 3 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-accounts.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-accounts.json new file mode 100644 index 000000000..d2636341a --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-accounts.json @@ -0,0 +1,122 @@ +{ + "data": [ + { + "id": "act_123456789", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + } + ], + "account_id": 123456789, + "account_status": 1, + "age": 10.0036226851851852, + "amount_spent": "1234", + "balance": "10000", + "business_city": "Poznan", + "business_country_code": "PL", + "business_name": "Some business name for the account", + "business_state": "wielkopolska", + "business_street": "Some street 79A", + "business_zip": "66-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-02-19T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": "93263", + "end_advertiser": 987654321, + "funding_source": "1122334455", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": "0", + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + } + ] + }, + "tax_id_status": 3 + }, + { + "id": "act_77777777", + "account_groups": [ + { + "account_group_id": "987654321", + "name": "Test group name", + "status": 1 + }, + { + "account_group_id": "11223344", + "name": "Test group name 2", + "status": 1 + } + + ], + "account_id": 77777777, + "account_status": 2, + "age": 777.777, + "amount_spent": 7777, + "balance": 77777, + "business_city": "Warsaw", + "business_country_code": "PL", + "business_name": "Some business name for the account 2", + "business_state": "mazowieckie", + "business_street": "Some street 2", + "business_zip": "77-777", + "capabilities": [ + "DIRECT_SALES", + "VIEW_TAGS" + ], + "created_time": "2015-04-20T00:31:33+0100", + "currency": "PLN", + "daily_spend_limit": 77, + "end_advertiser": 987654321, + "funding_source": "77", + "is_personal": 1, + "media_agency": 54321, + "name": "This is a test account 2", + "offsite_pixels_tos_accepted": false, + "partner": 111222333, + "spend_cap": 0, + "timezone_id": 106, + "timezone_name": "Europe/Warsaw", + "timezone_offset_hours_utc": 2, + "users": { + "data": [ + { + "id": "1234", + "permissions": [ + 1, + 2, + 3 + ], + "role": 1001 + } + ] + }, + "tax_id_status": 5 + } + ], + "paging": { + "cursors": { + "before": "NjAyMjEyODA0OTQ1MA==", + "after": "NjAyMjEyODA0OTQ1MA==" + } + } +} \ No newline at end of file From 6cc06ede7d3d9118f62712d67653d60b0be9b55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Mon, 4 May 2015 16:07:51 +0200 Subject: [PATCH 02/28] Defines campaign operations. Adds support for getting campaign info. --- .../social/facebook/api/AdCampaign.java | 61 +++++++++ .../facebook/api/CampaignOperations.java | 126 ++++++++++++++++++ .../social/facebook/api/FacebookAds.java | 1 + .../facebook/api/impl/CampaignTemplate.java | 59 ++++++++ .../api/impl/FacebookAdsTemplate.java | 7 + .../api/impl/json/AdCampaignMixin.java | 81 +++++++++++ .../api/impl/json/FacebookModule.java | 60 +-------- .../facebook/api/CampaignTemplateTest.java | 61 +++++++++ .../api/ad-campaign-with-unknown-enums.json | 8 ++ .../social/facebook/api/ad-campaign.json | 9 ++ 10 files changed, 415 insertions(+), 58 deletions(-) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java create mode 100644 spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign-with-unknown-enums.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java new file mode 100644 index 000000000..2c5a432aa --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java @@ -0,0 +1,61 @@ +package org.springframework.social.facebook.api; + +/** + * Model class representing an ad campaign. + * + * @author Sebastian Górecki + */ +public class AdCampaign extends FacebookObject { + private String id; + private String accountId; + private BuyingType buyingType; + private CampaignStatus campaignStatus; + private String name; + private CampaignObjective objective; + private int spendCap; + + public AdCampaign() { + } + + public CampaignStatus getCampaignStatus() { + return campaignStatus; + } + + public String getName() { + return name; + } + + public String getId() { + return id; + } + + public String getAccountId() { + return accountId; + } + + public BuyingType getBuyingType() { + return buyingType; + } + + public CampaignObjective getObjective() { + return objective; + } + + public int getSpendCap() { + return spendCap; + } + + public enum BuyingType { + AUCTION, FIXED_CPM, RESERVED, MIXED, UNKNOWN + } + + public enum CampaignStatus { + ACTIVE, PAUSED, ARCHIVED, DELETED, UNKNOWN + } + + public enum CampaignObjective { + CANVAS_APP_ENGAGEMENT, CANVAS_APP_INSTALLS, EVENT_RESPONSES, LOCAL_AWARENESS, MOBILE_APP_ENGAGEMENT, + MOBILE_APP_INSTALLS, NONE, OFFER_CLAIMS, PAGE_LIKES, POST_ENGAGEMENT, VIDEO_VIEWS, WEBSITE_CLICKS, + WEBSITE_CONVERSIONS, UNKNOWN + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java new file mode 100644 index 000000000..4ca681aa7 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java @@ -0,0 +1,126 @@ +package org.springframework.social.facebook.api; + +import org.springframework.social.ApiException; +import org.springframework.social.InsufficientPermissionException; +import org.springframework.social.MissingAuthorizationException; +import org.springframework.social.facebook.api.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; + +/** + * Defines operations for working with Facebook Ad Campaign object. + * + * @author Sebastian Górecki + */ +public interface CampaignOperations { + + static final String[] AD_CAMPAIGN_FIELDS = { + "id", "account_id", "buying_type", "campaign_group_status", "name", "objective", "spend_cap" + }; + + /** + * Get the campaign by given id. + * + * @param id the id of the campaign + * @return the {@link AdCampaign} object. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + AdCampaign getAdCampaign(String id); + + /** + * Creates new campaign with given name and status. + * + * @param name the name of new campaign + * @param status the status of the new campaign (ACTIVE or PAUSED) + * @return the id of the ad campaign created + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + int createAdCampaign(String name, CampaignStatus status); + + /** + * Creates new campaign with given name, status, objective and spend cap. + * + * @param name the name of the new campaign + * @param status the status of the new campaign (ACTIVE or PAUSED) + * @param objective the objective of this ad campaign + * @param spendCap the spend cap for the campaign, defined as value of cents in your currency + * @return the id of the ad campaign created + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + int createAdCampaign(String name, CampaignStatus status, CampaignObjective objective, int spendCap); + + /** + * Creates new campaign with given name, status, objective, spend cap and buying type. + * + * @param name the name of the new campaign + * @param status the status of the new campaign (ACTIVE or PAUSED) + * @param objective the objective of this ad campaign + * @param spendCap the spend cap for the campaign, defined as value of cents in your currency + * @param buyingType buying type - this field will help Facebook make future optimizations to delivery, pricing, and limits + * @return the id of the ad campaign created + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + int createAdCampaign(String name, CampaignStatus status, CampaignObjective objective, int spendCap, BuyingType buyingType); + + /** + * Updates the name of the ad campaign. + * + * @param campaignId the id of ad campaign + * @param name new name of the campaign + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void updateAdCampaignName(String campaignId, String name); + + /** + * Updates the status of the ad campaign. + * + * @param campaignId the id of ad campaign + * @param status new status of the campaign + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void updateAdCampaignStatus(String campaignId, CampaignStatus status); + + /** + * Updates the objective of the ad campaign. + * + * @param campaignId the id of ad campaign + * @param objective new objective of the campaign + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void updateAdCampaignObjective(String campaignId, CampaignObjective objective); + + /** + * Updates spend cap of the ad campaign. + * + * @param campaignId the id of ad campaign + * @param spendCap new spend cap + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void updateAdCampaignSpendCap(String campaignId, int spendCap); + + /** + * Deletes the ad campaign given by id. + * + * @param id the id of the campaign to delete + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void deleteAdCampaign(String id); +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java index e6e9fabe9..5400b6ba3 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java @@ -10,4 +10,5 @@ */ public interface FacebookAds { AccountOperations accountOperations(); + CampaignOperations campaignOperations(); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java new file mode 100644 index 000000000..6b42f6137 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java @@ -0,0 +1,59 @@ +package org.springframework.social.facebook.api.impl; + +import org.springframework.social.facebook.api.AdCampaign; +import org.springframework.social.facebook.api.CampaignOperations; +import org.springframework.social.facebook.api.GraphApi; +import org.springframework.web.client.RestTemplate; + +/** + * @author Sebastian Górecki + */ +public class CampaignTemplate extends AbstractFacebookOperations implements CampaignOperations { + + private final GraphApi graphApi; + private final RestTemplate restTemplate; + + + public CampaignTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean isAuthorizedForUser) { + super(isAuthorizedForUser); + this.graphApi = graphApi; + this.restTemplate = restTemplate; + } + + public AdCampaign getAdCampaign(String id) { + requireAuthorization(); + return graphApi.fetchObject(id, AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); + } + + public int createAdCampaign(String name, AdCampaign.CampaignStatus status) { + return 0; + } + + public int createAdCampaign(String name, AdCampaign.CampaignStatus status, AdCampaign.CampaignObjective objective, int spendCap) { + return 0; + } + + public int createAdCampaign(String name, AdCampaign.CampaignStatus status, AdCampaign.CampaignObjective objective, int spendCap, AdCampaign.BuyingType buyingType) { + return 0; + } + + public void updateAdCampaignName(String campaignId, String name) { + + } + + public void updateAdCampaignStatus(String campaignId, AdCampaign.CampaignStatus status) { + + } + + public void updateAdCampaignObjective(String campaignId, AdCampaign.CampaignObjective objective) { + + } + + public void updateAdCampaignSpendCap(String campaignId, int spendCap) { + + } + + public void deleteAdCampaign(String id) { + + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java index 987d37cb4..6fa84e642 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java @@ -1,6 +1,7 @@ package org.springframework.social.facebook.api.impl; import org.springframework.social.facebook.api.AccountOperations; +import org.springframework.social.facebook.api.CampaignOperations; import org.springframework.social.facebook.api.FacebookAds; /** @@ -11,6 +12,7 @@ public class FacebookAdsTemplate extends FacebookTemplate implements FacebookAds { private AccountOperations accountOperations; + private CampaignOperations campaignOperations; public FacebookAdsTemplate() { super(null); @@ -26,7 +28,12 @@ public AccountOperations accountOperations() { return accountOperations; } + public CampaignOperations campaignOperations() { + return campaignOperations; + } + private void initSubApis() { accountOperations = new AccountTemplate(this, getRestTemplate(), isAuthorized()); + campaignOperations = new CampaignTemplate(this, getRestTemplate(), isAuthorized()); } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java new file mode 100644 index 000000000..b0732be69 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java @@ -0,0 +1,81 @@ +package org.springframework.social.facebook.api.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.social.facebook.api.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; + +import java.io.IOException; + +/** + * Annotated mixin to add Jackson annotations to AdCampaign. + * + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract public class AdCampaignMixin extends FacebookObjectMixin { + + @JsonProperty("id") + private String id; + + @JsonProperty("account_id") + private String accountId; + + @JsonProperty("buying_type") + @JsonDeserialize(using = BuyingTypeDeserializer.class) + private BuyingType buyingType; + + @JsonProperty("campaign_group_status") + @JsonDeserialize(using = CampaignStatusDeserializer.class) + private CampaignStatus campaignStatus; + + @JsonProperty("name") + private String name; + + @JsonProperty("objective") + @JsonDeserialize(using = CampaignObjectiveDeserializer.class) + private CampaignObjective objective; + + @JsonProperty("spend_cap") + private int spendCap; + + + private static class BuyingTypeDeserializer extends JsonDeserializer { + @Override + public BuyingType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + return BuyingType.valueOf(jp.getValueAsString().toUpperCase()); + } catch (IllegalArgumentException e) { + return BuyingType.UNKNOWN; + } + } + } + + private static class CampaignStatusDeserializer extends JsonDeserializer { + @Override + public CampaignStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + return CampaignStatus.valueOf(jp.getValueAsString().toUpperCase()); + } catch (IllegalArgumentException e) { + return CampaignStatus.UNKNOWN; + } + } + } + + private static class CampaignObjectiveDeserializer extends JsonDeserializer { + @Override + public CampaignObjective deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + return CampaignObjective.valueOf(jp.getValueAsString().toUpperCase()); + } catch (IllegalArgumentException e) { + return CampaignObjective.UNKNOWN; + } + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java index 5f9b65e14..5deb68094 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java @@ -15,65 +15,9 @@ */ package org.springframework.social.facebook.api.impl.json; -import org.springframework.social.facebook.api.Account; -import org.springframework.social.facebook.api.Achievement; -import org.springframework.social.facebook.api.AchievementType; -import org.springframework.social.facebook.api.Action; -import org.springframework.social.facebook.api.AdAccount; -import org.springframework.social.facebook.api.AdAccountGroup; -import org.springframework.social.facebook.api.AdInsight; -import org.springframework.social.facebook.api.AdInsightAction; -import org.springframework.social.facebook.api.AdUser; -import org.springframework.social.facebook.api.Album; -import org.springframework.social.facebook.api.ApplicationReference; -import org.springframework.social.facebook.api.Comment; -import org.springframework.social.facebook.api.CoverPhoto; -import org.springframework.social.facebook.api.Currency; -import org.springframework.social.facebook.api.Device; -import org.springframework.social.facebook.api.EducationExperience; -import org.springframework.social.facebook.api.Engagement; -import org.springframework.social.facebook.api.Event; -import org.springframework.social.facebook.api.EventInvitee; -import org.springframework.social.facebook.api.Experience; -import org.springframework.social.facebook.api.FamilyMember; -import org.springframework.social.facebook.api.FriendList; -import org.springframework.social.facebook.api.Group; -import org.springframework.social.facebook.api.GroupMemberReference; -import org.springframework.social.facebook.api.GroupMembership; -import org.springframework.social.facebook.api.ImageSource; -import org.springframework.social.facebook.api.Invitation; -import org.springframework.social.facebook.api.Location; -import org.springframework.social.facebook.api.MailingAddress; -import org.springframework.social.facebook.api.MessageTag; -import org.springframework.social.facebook.api.Page; -import org.springframework.social.facebook.api.PageParking; -import org.springframework.social.facebook.api.PagePaymentOptions; -import org.springframework.social.facebook.api.PageRestaurantServices; -import org.springframework.social.facebook.api.PageRestaurantSpecialties; -import org.springframework.social.facebook.api.PaymentPricePoint; -import org.springframework.social.facebook.api.PaymentPricePoints; -import org.springframework.social.facebook.api.Photo; +import org.springframework.social.facebook.api.*; import org.springframework.social.facebook.api.Photo.Image; -import org.springframework.social.facebook.api.Place; -import org.springframework.social.facebook.api.PlaceTag; -import org.springframework.social.facebook.api.Post; -import org.springframework.social.facebook.api.PostProperty; -import org.springframework.social.facebook.api.ProfilePictureSource; -import org.springframework.social.facebook.api.Reference; -import org.springframework.social.facebook.api.RestaurantServices; -import org.springframework.social.facebook.api.SecuritySettings; -import org.springframework.social.facebook.api.StoryAttachment; -import org.springframework.social.facebook.api.Tag; -import org.springframework.social.facebook.api.TestUser; -import org.springframework.social.facebook.api.User; -import org.springframework.social.facebook.api.UserIdForApp; -import org.springframework.social.facebook.api.UserInvitableFriend; -import org.springframework.social.facebook.api.UserTaggableFriend; -import org.springframework.social.facebook.api.Video; import org.springframework.social.facebook.api.Video.VideoFormat; -import org.springframework.social.facebook.api.VideoUploadLimits; -import org.springframework.social.facebook.api.VoipInfo; -import org.springframework.social.facebook.api.WorkEntry; import org.springframework.social.facebook.api.WorkEntry.Project; import org.springframework.social.facebook.api.impl.json.PhotoMixin.ImageMixin; import org.springframework.social.facebook.api.impl.json.VideoMixin.VideoFormatMixin; @@ -185,6 +129,6 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(AdInsight.class, AdInsightMixin.class); context.setMixInAnnotations(AdAccount.class, AdAccountMixin.class); - + context.setMixInAnnotations(AdCampaign.class, AdCampaignMixin.class); } } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java new file mode 100644 index 000000000..6011cd9fb --- /dev/null +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java @@ -0,0 +1,61 @@ +package org.springframework.social.facebook.api; + +import org.junit.Test; +import org.springframework.http.MediaType; + +import org.springframework.social.NotAuthorizedException; +import org.springframework.social.facebook.api.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; + +import static org.junit.Assert.assertEquals; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * @author Sebastian Górecki + */ +public class CampaignTemplateTest extends AbstractFacebookAdsApiTest { + + @Test + public void getCampaign() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789?fields=id%2Caccount_id%2Cbuying_type%2Ccampaign_group_status%2Cname%2Cobjective%2Cspend_cap")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-campaign"), MediaType.APPLICATION_JSON)); + + AdCampaign campaign = facebookAds.campaignOperations().getAdCampaign("600123456789"); + assertEquals("600123456789", campaign.getId()); + assertEquals("123456789", campaign.getAccountId()); + assertEquals(BuyingType.AUCTION, campaign.getBuyingType()); + assertEquals(CampaignStatus.ACTIVE, campaign.getCampaignStatus()); + assertEquals("The test campaign name", campaign.getName()); + assertEquals(CampaignObjective.POST_ENGAGEMENT, campaign.getObjective()); + assertEquals(1000, campaign.getSpendCap()); + } + + @Test + public void getCampaign_withUnknownEnums() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789?fields=id%2Caccount_id%2Cbuying_type%2Ccampaign_group_status%2Cname%2Cobjective%2Cspend_cap")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-campaign-with-unknown-enums"), MediaType.APPLICATION_JSON)); + + AdCampaign campaign = facebookAds.campaignOperations().getAdCampaign("600123456789"); + assertEquals("600123456789", campaign.getId()); + assertEquals("123456789", campaign.getAccountId()); + assertEquals(BuyingType.UNKNOWN, campaign.getBuyingType()); + assertEquals(CampaignStatus.UNKNOWN, campaign.getCampaignStatus()); + assertEquals("The test campaign name", campaign.getName()); + assertEquals(CampaignObjective.UNKNOWN, campaign.getObjective()); + + } + + @Test(expected = NotAuthorizedException.class) + public void getCampaign_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().getAdCampaign("600123456789"); + } + + +} diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign-with-unknown-enums.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign-with-unknown-enums.json new file mode 100644 index 000000000..d1489a710 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign-with-unknown-enums.json @@ -0,0 +1,8 @@ +{ + "id": "600123456789", + "account_id": "123456789", + "buying_type": "OTHER", + "campaign_group_status": "DEACTIVATED", + "name": "The test campaign name", + "objective": "AD_SELLING" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign.json new file mode 100644 index 000000000..cf9511acc --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign.json @@ -0,0 +1,9 @@ +{ + "id": "600123456789", + "account_id": "123456789", + "buying_type": "AUCTION", + "campaign_group_status": "ACTIVE", + "name": "The test campaign name", + "objective": "POST_ENGAGEMENT", + "spend_cap": 1000 +} \ No newline at end of file From e60c694880364d168b2c75df510378237c6eb2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Tue, 5 May 2015 12:23:32 +0200 Subject: [PATCH 03/28] Adds support for creating ad campaign. --- .../facebook/api/CampaignOperations.java | 36 ++++++-- .../api/InvalidCampaignStatusException.java | 12 +++ .../api/InvalidParameterException.java | 15 ++++ .../facebook/api/impl/CampaignTemplate.java | 38 +++++++-- .../api/impl/FacebookErrorHandler.java | 3 + .../facebook/api/CampaignTemplateTest.java | 82 +++++++++++++++++++ .../error-invalid-update-campaign-status.json | 11 +++ 7 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidCampaignStatusException.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidParameterException.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-invalid-update-campaign-status.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java index 4ca681aa7..e0b1c0fd1 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java @@ -32,43 +32,63 @@ public interface CampaignOperations { /** * Creates new campaign with given name and status. * - * @param name the name of new campaign - * @param status the status of the new campaign (ACTIVE or PAUSED) + * @param accountId the id of the ad account + * @param name the name of new campaign + * @param status the status of the new campaign (ACTIVE or PAUSED) + * @return the id of the ad campaign created + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign + */ + String createAdCampaign(String accountId, String name, CampaignStatus status); + + /** + * Creates new campaign with given name, status and objective. + * + * @param accountId the id of the ad account + * @param name the name of the new campaign + * @param status the status of the new campaign (ACTIVE or PAUSED) + * @param objective the objective of this ad campaign * @return the id of the ad campaign created * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - int createAdCampaign(String name, CampaignStatus status); + String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective); /** * Creates new campaign with given name, status, objective and spend cap. * + * @param accountId the id of the ad account * @param name the name of the new campaign * @param status the status of the new campaign (ACTIVE or PAUSED) * @param objective the objective of this ad campaign - * @param spendCap the spend cap for the campaign, defined as value of cents in your currency + * @param spendCap the spend cap for the campaign defined as integer value of cents in your currency * @return the id of the ad campaign created * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - int createAdCampaign(String name, CampaignStatus status, CampaignObjective objective, int spendCap); + String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, int spendCap); /** - * Creates new campaign with given name, status, objective, spend cap and buying type. + * Creates new campaign with given name, status, objective and buying type. * + * @param accountId the id of the ad account * @param name the name of the new campaign * @param status the status of the new campaign (ACTIVE or PAUSED) * @param objective the objective of this ad campaign - * @param spendCap the spend cap for the campaign, defined as value of cents in your currency * @param buyingType buying type - this field will help Facebook make future optimizations to delivery, pricing, and limits * @return the id of the ad campaign created * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - int createAdCampaign(String name, CampaignStatus status, CampaignObjective objective, int spendCap, BuyingType buyingType); + String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, BuyingType buyingType); /** * Updates the name of the ad campaign. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidCampaignStatusException.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidCampaignStatusException.java new file mode 100644 index 000000000..e2136ddce --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidCampaignStatusException.java @@ -0,0 +1,12 @@ +package org.springframework.social.facebook.api; + +/** + * Exception is thrown when a new ad campaign is created with status other that ACTIVE or PAUSED. + * + * @author Sebastian Górecki + */ +public class InvalidCampaignStatusException extends InvalidParameterException { + public InvalidCampaignStatusException(String providerId, String message) { + super(providerId, message); + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidParameterException.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidParameterException.java new file mode 100644 index 000000000..86d3dc3a4 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidParameterException.java @@ -0,0 +1,15 @@ +package org.springframework.social.facebook.api; + +import org.springframework.social.ApiException; + +/** + * Exception thrown when one of the supplied parameters is invalid. + * + * @author Sebastian Górecki + */ +public class InvalidParameterException extends ApiException { + + public InvalidParameterException(String providerId, String message) { + super(providerId, message); + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java index 6b42f6137..b0d7880ea 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java @@ -1,8 +1,13 @@ package org.springframework.social.facebook.api.impl; import org.springframework.social.facebook.api.AdCampaign; +import org.springframework.social.facebook.api.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; import org.springframework.social.facebook.api.CampaignOperations; import org.springframework.social.facebook.api.GraphApi; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; /** @@ -25,18 +30,39 @@ public AdCampaign getAdCampaign(String id) { return graphApi.fetchObject(id, AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); } - public int createAdCampaign(String name, AdCampaign.CampaignStatus status) { - return 0; + public String createAdCampaign(String accountId, String name, CampaignStatus status) { + return createAdCampaign(accountId, name, status, null, null); } - public int createAdCampaign(String name, AdCampaign.CampaignStatus status, AdCampaign.CampaignObjective objective, int spendCap) { - return 0; + public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective) { + return createAdCampaign(accountId, name, status, objective, null); } - public int createAdCampaign(String name, AdCampaign.CampaignStatus status, AdCampaign.CampaignObjective objective, int spendCap, AdCampaign.BuyingType buyingType) { - return 0; + public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, int spendCap) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.add("name", name); + map.add("campaign_group_status", status.name()); + map.add("objective", objective.name()); + map.add("spend_cap", String.valueOf(spendCap)); + return graphApi.publish("act_" + accountId, "adcampaign_groups", map); } + public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, BuyingType buyingType) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.add("name", name); + map.add("campaign_group_status", status.name()); + if (objective != null) { + map.add("objective", objective.name()); + } + if (buyingType != null) { + map.add("buying_type", buyingType.name()); + } + return graphApi.publish("act_" + accountId, "adcampaign_groups", map); + } + + public void updateAdCampaignName(String campaignId, String name) { } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java index fc695c500..4562e7d44 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java @@ -36,6 +36,7 @@ import org.springframework.social.ServerException; import org.springframework.social.UncategorizedApiException; import org.springframework.social.facebook.api.FacebookError; +import org.springframework.social.facebook.api.InvalidCampaignStatusException; import org.springframework.web.client.DefaultResponseErrorHandler; import com.fasterxml.jackson.core.JsonFactory; @@ -87,6 +88,8 @@ void handleFacebookError(HttpStatus statusCode, FacebookError error) { throw new DuplicateStatusException(FACEBOOK_PROVIDER_ID, error.getMessage()); } else if (code == DATA_OBJECT_NOT_FOUND || code == PATH_UNKNOWN) { throw new ResourceNotFoundException(FACEBOOK_PROVIDER_ID, error.getMessage()); + } else if (code == PARAM && error.getSubcode() == 1487564) { + throw new InvalidCampaignStatusException(FACEBOOK_PROVIDER_ID, error.getUserMessage()); } else { throw new UncategorizedApiException(FACEBOOK_PROVIDER_ID, error.getMessage(), null); } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java index 6011cd9fb..254ea47df 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java @@ -9,8 +9,11 @@ import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withBadRequest; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; /** @@ -57,5 +60,84 @@ public void getCampaign_unauthorized() throws Exception { unauthorizedFacebookAds.campaignOperations().getAdCampaign("600123456789"); } + @Test + public void createCampaign_withNameAndStatus() throws Exception { + String requestBody = "name=Campaign+created+by+SpringSocialFacebook&campaign_group_status=PAUSED"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED)); + mockServer.verify(); + } + @Test + public void createCampaign_withInvalidStatus() throws Exception { + // new campaigns can be created only with statuses ACTIVE or PAUSED + String requestBody = "name=Campaign+with+invalid+status&campaign_group_status=ARCHIVED"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withBadRequest().body(jsonResource("error-invalid-update-campaign-status")).contentType(MediaType.APPLICATION_JSON)); + + try { + facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with invalid status", CampaignStatus.ARCHIVED); + fail(); + } catch (InvalidCampaignStatusException e) { + assertEquals("New campaigns need to be either active or paused.", e.getMessage()); + assertEquals("facebook", e.getProviderId()); + } + } + + @Test + public void createCampaign_withObjective() throws Exception { + String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with objective", + CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES)); + mockServer.verify(); + } + + @Test + public void createCampaign_withBuyingType() throws Exception { + String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES&buying_type=AUCTION"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with objective", + CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, BuyingType.AUCTION)); + mockServer.verify(); + } + + @Test + public void createCampaign_withSpendCap() throws Exception { + String requestBody = "name=Campaign+with+spend+cap&campaign_group_status=ACTIVE&objective=PAGE_LIKES&spend_cap=50000"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with spend cap", + CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, 50000)); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void createCampaign_withSpendCap_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with spend cap", + CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, 50000); + } + + @Test(expected = NotAuthorizedException.class) + public void createCampaign_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().createAdCampaign("123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED); + } } diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-invalid-update-campaign-status.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-invalid-update-campaign-status.json new file mode 100644 index 000000000..df66933d9 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-invalid-update-campaign-status.json @@ -0,0 +1,11 @@ +{ + "error": { + "message": "Invalid parameter", + "type": "FacebookApiException", + "code": 100, + "error_subcode": 1487564, + "is_transient": false, + "error_user_title": "Update Campaign Status", + "error_user_msg": "New campaigns need to be either active or paused." + } +} \ No newline at end of file From b3b1c7badac526d14896ebf6532098901c0a71ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Tue, 5 May 2015 12:42:36 +0200 Subject: [PATCH 04/28] Changing the create campaign interface. --- .../social/facebook/api/AdCampaign.java | 4 +-- .../facebook/api/CampaignOperations.java | 22 +++------------ .../facebook/api/impl/CampaignTemplate.java | 20 ++++--------- .../api/impl/json/AdCampaignMixin.java | 2 +- .../facebook/api/CampaignTemplateTest.java | 28 ++++++++----------- 5 files changed, 24 insertions(+), 52 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java index 2c5a432aa..c1284f8bd 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java @@ -12,7 +12,7 @@ public class AdCampaign extends FacebookObject { private CampaignStatus campaignStatus; private String name; private CampaignObjective objective; - private int spendCap; + private String spendCap; public AdCampaign() { } @@ -41,7 +41,7 @@ public CampaignObjective getObjective() { return objective; } - public int getSpendCap() { + public String getSpendCap() { return spendCap; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java index e0b1c0fd1..bc5516317 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java @@ -50,37 +50,23 @@ public interface CampaignOperations { * @param name the name of the new campaign * @param status the status of the new campaign (ACTIVE or PAUSED) * @param objective the objective of this ad campaign + * @param spendCap the spend cap for the campaign defined as value of cents in your currency, set to null to allow unlimited spend. A minimum value is $100 USD (or approximate local equivalent). * @return the id of the ad campaign created * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective); + String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap); /** * Creates new campaign with given name, status, objective and spend cap. * - * @param accountId the id of the ad account - * @param name the name of the new campaign - * @param status the status of the new campaign (ACTIVE or PAUSED) - * @param objective the objective of this ad campaign - * @param spendCap the spend cap for the campaign defined as integer value of cents in your currency - * @return the id of the ad campaign created - * @throws ApiException if there is an error while communicating with Facebook. - * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. - * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign - */ - String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, int spendCap); - - /** - * Creates new campaign with given name, status, objective and buying type. - * * @param accountId the id of the ad account * @param name the name of the new campaign * @param status the status of the new campaign (ACTIVE or PAUSED) * @param objective the objective of this ad campaign + * @param spendCap the spend cap for the campaign defined as value of cents in your currency, set to null to allow unlimited spend. A minimum value is $100 USD (or approximate local equivalent). * @param buyingType buying type - this field will help Facebook make future optimizations to delivery, pricing, and limits * @return the id of the ad campaign created * @throws ApiException if there is an error while communicating with Facebook. @@ -88,7 +74,7 @@ public interface CampaignOperations { * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, BuyingType buyingType); + String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType); /** * Updates the name of the ad campaign. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java index b0d7880ea..cd4e76328 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java @@ -34,21 +34,11 @@ public String createAdCampaign(String accountId, String name, CampaignStatus sta return createAdCampaign(accountId, name, status, null, null); } - public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective) { - return createAdCampaign(accountId, name, status, objective, null); + public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap) { + return createAdCampaign(accountId, name, status, objective, spendCap, null); } - public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, int spendCap) { - requireAuthorization(); - MultiValueMap map = new LinkedMultiValueMap(); - map.add("name", name); - map.add("campaign_group_status", status.name()); - map.add("objective", objective.name()); - map.add("spend_cap", String.valueOf(spendCap)); - return graphApi.publish("act_" + accountId, "adcampaign_groups", map); - } - - public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, BuyingType buyingType) { + public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType) { requireAuthorization(); MultiValueMap map = new LinkedMultiValueMap(); map.add("name", name); @@ -56,13 +46,15 @@ public String createAdCampaign(String accountId, String name, CampaignStatus sta if (objective != null) { map.add("objective", objective.name()); } + if (spendCap != null) { + map.add("spend_cap", spendCap); + } if (buyingType != null) { map.add("buying_type", buyingType.name()); } return graphApi.publish("act_" + accountId, "adcampaign_groups", map); } - public void updateAdCampaignName(String campaignId, String name) { } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java index b0732be69..be77fb53f 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java @@ -43,7 +43,7 @@ abstract public class AdCampaignMixin extends FacebookObjectMixin { private CampaignObjective objective; @JsonProperty("spend_cap") - private int spendCap; + private String spendCap; private static class BuyingTypeDeserializer extends JsonDeserializer { diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java index 254ea47df..1eea8e70f 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java @@ -35,7 +35,7 @@ public void getCampaign() throws Exception { assertEquals(CampaignStatus.ACTIVE, campaign.getCampaignStatus()); assertEquals("The test campaign name", campaign.getName()); assertEquals(CampaignObjective.POST_ENGAGEMENT, campaign.getObjective()); - assertEquals(1000, campaign.getSpendCap()); + assertEquals("1000", campaign.getSpendCap()); } @Test @@ -93,49 +93,43 @@ public void createCampaign_withInvalidStatus() throws Exception { @Test public void createCampaign_withObjective() throws Exception { - String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES"; + String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES&spend_cap=50000"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with objective", - CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES)); + CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, "50000")); mockServer.verify(); } @Test - public void createCampaign_withBuyingType() throws Exception { - String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES&buying_type=AUCTION"; + public void createCampaign_withoutSpendCap() throws Exception { + String requestBody = "name=Campaign+with+spend+cap&campaign_group_status=ACTIVE&objective=PAGE_LIKES"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with objective", - CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, BuyingType.AUCTION)); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with spend cap", + CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, null)); mockServer.verify(); } @Test - public void createCampaign_withSpendCap() throws Exception { - String requestBody = "name=Campaign+with+spend+cap&campaign_group_status=ACTIVE&objective=PAGE_LIKES&spend_cap=50000"; + public void createCampaign_withBuyingType() throws Exception { + String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES&spend_cap=50000&buying_type=AUCTION"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with spend cap", - CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, 50000)); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with objective", + CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, "50000", BuyingType.AUCTION)); mockServer.verify(); } - @Test(expected = NotAuthorizedException.class) - public void createCampaign_withSpendCap_unauthorized() throws Exception { - unauthorizedFacebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with spend cap", - CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, 50000); - } - @Test(expected = NotAuthorizedException.class) public void createCampaign_unauthorized() throws Exception { unauthorizedFacebookAds.campaignOperations().createAdCampaign("123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED); From fa50a842f900a6e8e4cfe1db9223d8ae64a56d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Tue, 5 May 2015 14:48:06 +0200 Subject: [PATCH 05/28] Adds support for updating and deleting ad campaign. --- .../facebook/api/CampaignOperations.java | 4 +- .../facebook/api/impl/CampaignTemplate.java | 31 +++++-- .../facebook/api/CampaignTemplateTest.java | 85 +++++++++++++++++++ 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java index bc5516317..73a4033a7 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java @@ -123,10 +123,10 @@ public interface CampaignOperations { /** * Deletes the ad campaign given by id. * - * @param id the id of the campaign to delete + * @param campaignId the id of the campaign to delete * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - void deleteAdCampaign(String id); + void deleteAdCampaign(String campaignId); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java index cd4e76328..f1f0a728a 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java @@ -56,22 +56,37 @@ public String createAdCampaign(String accountId, String name, CampaignStatus sta } public void updateAdCampaignName(String campaignId, String name) { - + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.add("name", name); + graphApi.post(campaignId, map); } - public void updateAdCampaignStatus(String campaignId, AdCampaign.CampaignStatus status) { - + public void updateAdCampaignStatus(String campaignId, CampaignStatus status) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.add("status", status.name()); + graphApi.post(campaignId, map); } - public void updateAdCampaignObjective(String campaignId, AdCampaign.CampaignObjective objective) { - + public void updateAdCampaignObjective(String campaignId, CampaignObjective objective) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.add("objective", objective.name()); + graphApi.post(campaignId, map); } public void updateAdCampaignSpendCap(String campaignId, int spendCap) { - + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.add("spend_cap", String.valueOf(spendCap)); + graphApi.post(campaignId, map); } - public void deleteAdCampaign(String id) { - + public void deleteAdCampaign(String campaignId) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.add("campaign_group_status", CampaignStatus.DELETED.name()); + graphApi.post(campaignId, map); } } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java index 1eea8e70f..7469da62b 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java @@ -134,4 +134,89 @@ public void createCampaign_withBuyingType() throws Exception { public void createCampaign_unauthorized() throws Exception { unauthorizedFacebookAds.campaignOperations().createAdCampaign("123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED); } + + @Test + public void updateAdCampaignName() throws Exception { + String requestBody = "name=New+campaign+name"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.campaignOperations().updateAdCampaignName("600123456789", "New campaign name"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAdCampaignName_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().updateAdCampaignName("600123456789", "New campaign name"); + } + + @Test + public void updateAdCampaignStatus() throws Exception { + String requestBody = "status=ACTIVE"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.campaignOperations().updateAdCampaignStatus("600123456789", CampaignStatus.ACTIVE); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAdCampaignStatus_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().updateAdCampaignStatus("600123456789", CampaignStatus.ACTIVE); + } + + @Test + public void updateAdCampaignObjective() throws Exception { + String requestBody = "objective=POST_ENGAGEMENT"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.campaignOperations().updateAdCampaignObjective("600123456789", CampaignObjective.POST_ENGAGEMENT); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAdCampaignObjective_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().updateAdCampaignObjective("600123456789", CampaignObjective.POST_ENGAGEMENT); + } + + @Test + public void updateAdCampaignSpendCap() throws Exception { + String requestBody = "spend_cap=60000"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.campaignOperations().updateAdCampaignSpendCap("600123456789", 60000); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAdCampaignSpendCap_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().updateAdCampaignSpendCap("600123456789", 60000); + } + + @Test + public void deleteAdCampaign() throws Exception { + String requestBody = "campaign_group_status=DELETED"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.campaignOperations().deleteAdCampaign("600123456789"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void deleteAdCampaign_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().deleteAdCampaign("600123456789"); + } } From 46d0cfa69b73c0b8c2cecbb7546a1949e269e3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Wed, 6 May 2015 10:18:45 +0200 Subject: [PATCH 06/28] Adds possibility to get all campaigns associated with given ad account. --- .../facebook/api/AccountOperations.java | 7 ++++ .../facebook/api/impl/AccountTemplate.java | 4 +++ .../facebook/api/AccountTemplateTest.java | 34 ++++++++++++++++++ .../facebook/api/ad-account-campaigns.json | 35 +++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-campaigns.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java index 588884af9..e3801d293 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java @@ -53,6 +53,13 @@ public interface AccountOperations { */ AdAccount getAdAccount(String accountId); + /** + * Get all ad campaigns of an ad account. + * @param id the id of an ad account + * @return the list of {@link AdCampaign} objects + */ + PagedList getAdAccountCampaigns(String id); + /** * Get all users of the ad account. * diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java index d04f56a33..62a7fb211 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java @@ -33,6 +33,10 @@ public AdAccount getAdAccount(String id) { return graphApi.fetchObject(id, AdAccount.class, AD_ACCOUNT_FIELDS); } + public PagedList getAdAccountCampaigns(String id) { + return graphApi.fetchConnections(id, "adcampaign_groups", AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); + } + public List getAdAccountUsers(String accountId) { requireAuthorization(); return graphApi.fetchConnections(accountId, "users", AdUser.class); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java index 06a438739..4877ae7f1 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java @@ -7,6 +7,9 @@ import org.springframework.social.facebook.api.AdAccount.TaxStatus; import org.springframework.social.facebook.api.AdUser.AdUserPermission; import org.springframework.social.facebook.api.AdUser.AdUserRole; +import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; import java.util.List; @@ -206,6 +209,37 @@ public void getAdAccount_unauthorized() throws Exception { unauthorizedFacebookAds.accountOperations().getAdAccount("act_123456789"); } + @Test + public void getAdAccountCampaigns() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups?fields=id%2Caccount_id%2Cbuying_type%2Ccampaign_group_status%2Cname%2Cobjective%2Cspend_cap")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-campaigns"), MediaType.APPLICATION_JSON)); + PagedList campaigns = facebookAds.accountOperations().getAdAccountCampaigns("act_123456789"); + assertEquals(3, campaigns.size()); + assertEquals("601123456789", campaigns.get(0).getId()); + assertEquals("123456789", campaigns.get(0).getAccountId()); + assertEquals(BuyingType.AUCTION, campaigns.get(0).getBuyingType()); + assertEquals(CampaignStatus.ACTIVE, campaigns.get(0).getCampaignStatus()); + assertEquals("Campaign #1", campaigns.get(0).getName()); + assertEquals(CampaignObjective.POST_ENGAGEMENT, campaigns.get(0).getObjective()); + assertEquals(null, campaigns.get(0).getSpendCap()); + assertEquals("602123456789", campaigns.get(1).getId()); + assertEquals("123456789", campaigns.get(1).getAccountId()); + assertEquals(BuyingType.FIXED_CPM, campaigns.get(1).getBuyingType()); + assertEquals(CampaignStatus.PAUSED, campaigns.get(1).getCampaignStatus()); + assertEquals("Campaign #2", campaigns.get(1).getName()); + assertEquals(CampaignObjective.NONE, campaigns.get(1).getObjective()); + assertEquals(null, campaigns.get(1).getSpendCap()); + assertEquals("603123456789", campaigns.get(2).getId()); + assertEquals("123456789", campaigns.get(2).getAccountId()); + assertEquals(BuyingType.RESERVED, campaigns.get(2).getBuyingType()); + assertEquals(CampaignStatus.ARCHIVED, campaigns.get(2).getCampaignStatus()); + assertEquals("Campaign #3", campaigns.get(2).getName()); + assertEquals(CampaignObjective.WEBSITE_CONVERSIONS, campaigns.get(2).getObjective()); + assertEquals("50000", campaigns.get(2).getSpendCap()); + } + @Test public void getAccountUsers() throws Exception { mockServer.expect(requestTo(GET_ADACCOUNT_USERS_REQUEST_URI)) diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-campaigns.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-campaigns.json new file mode 100644 index 000000000..764a787db --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-campaigns.json @@ -0,0 +1,35 @@ +{ + "data": [ + { + "id": "601123456789", + "account_id": "123456789", + "buying_type": "AUCTION", + "campaign_group_status": "ACTIVE", + "name": "Campaign #1", + "objective": "POST_ENGAGEMENT" + }, + { + "id": "602123456789", + "account_id": "123456789", + "buying_type": "FIXED_CPM", + "campaign_group_status": "PAUSED", + "name": "Campaign #2", + "objective": "NONE" + }, + { + "id": "603123456789", + "account_id": "123456789", + "buying_type": "RESERVED", + "campaign_group_status": "ARCHIVED", + "name": "Campaign #3", + "objective": "WEBSITE_CONVERSIONS", + "spend_cap": 50000 + } + ], + "paging": { + "cursors": { + "before": "NjAyNDE2Nzg5NTY1MA==", + "after": "NjAyNTIwMDQ1MTQ1MA==" + } + } +} \ No newline at end of file From 7c62a3b8d1c0ed64ec109454aa7f8393056a6d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Wed, 6 May 2015 10:28:07 +0200 Subject: [PATCH 07/28] Corrects javadoc for getAdAccountCampaigns --- .../social/facebook/api/AccountOperations.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java index e3801d293..e3e0a5db4 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java @@ -55,8 +55,12 @@ public interface AccountOperations { /** * Get all ad campaigns of an ad account. + * * @param id the id of an ad account * @return the list of {@link AdCampaign} objects + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ PagedList getAdAccountCampaigns(String id); From 22d8f101c729f4ad18c211041b532553329cbdd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Wed, 6 May 2015 11:58:33 +0200 Subject: [PATCH 08/28] Fix problem with not updating ad campaign status. --- .../org/springframework/social/facebook/api/AdCampaign.java | 6 +++--- .../social/facebook/api/impl/CampaignTemplate.java | 2 +- .../social/facebook/api/impl/FacebookErrorHandler.java | 2 +- .../social/facebook/api/impl/json/AdCampaignMixin.java | 2 +- .../social/facebook/api/AccountTemplateTest.java | 6 +++--- .../social/facebook/api/CampaignTemplateTest.java | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java index c1284f8bd..08b35ef67 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java @@ -9,7 +9,7 @@ public class AdCampaign extends FacebookObject { private String id; private String accountId; private BuyingType buyingType; - private CampaignStatus campaignStatus; + private CampaignStatus status; private String name; private CampaignObjective objective; private String spendCap; @@ -17,8 +17,8 @@ public class AdCampaign extends FacebookObject { public AdCampaign() { } - public CampaignStatus getCampaignStatus() { - return campaignStatus; + public CampaignStatus getStatus() { + return status; } public String getName() { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java index f1f0a728a..c8bc0bc67 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java @@ -65,7 +65,7 @@ public void updateAdCampaignName(String campaignId, String name) { public void updateAdCampaignStatus(String campaignId, CampaignStatus status) { requireAuthorization(); MultiValueMap map = new LinkedMultiValueMap(); - map.add("status", status.name()); + map.add("campaign_group_status", status.name()); graphApi.post(campaignId, map); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java index 4562e7d44..66dd39d65 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java @@ -88,7 +88,7 @@ void handleFacebookError(HttpStatus statusCode, FacebookError error) { throw new DuplicateStatusException(FACEBOOK_PROVIDER_ID, error.getMessage()); } else if (code == DATA_OBJECT_NOT_FOUND || code == PATH_UNKNOWN) { throw new ResourceNotFoundException(FACEBOOK_PROVIDER_ID, error.getMessage()); - } else if (code == PARAM && error.getSubcode() == 1487564) { + } else if (code == PARAM && error.getSubcode() != null && error.getSubcode() == 1487564) { throw new InvalidCampaignStatusException(FACEBOOK_PROVIDER_ID, error.getUserMessage()); } else { throw new UncategorizedApiException(FACEBOOK_PROVIDER_ID, error.getMessage(), null); diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java index be77fb53f..9bee2b00b 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java @@ -33,7 +33,7 @@ abstract public class AdCampaignMixin extends FacebookObjectMixin { @JsonProperty("campaign_group_status") @JsonDeserialize(using = CampaignStatusDeserializer.class) - private CampaignStatus campaignStatus; + private CampaignStatus status; @JsonProperty("name") private String name; diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java index 4877ae7f1..76062483b 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java @@ -220,21 +220,21 @@ public void getAdAccountCampaigns() throws Exception { assertEquals("601123456789", campaigns.get(0).getId()); assertEquals("123456789", campaigns.get(0).getAccountId()); assertEquals(BuyingType.AUCTION, campaigns.get(0).getBuyingType()); - assertEquals(CampaignStatus.ACTIVE, campaigns.get(0).getCampaignStatus()); + assertEquals(CampaignStatus.ACTIVE, campaigns.get(0).getStatus()); assertEquals("Campaign #1", campaigns.get(0).getName()); assertEquals(CampaignObjective.POST_ENGAGEMENT, campaigns.get(0).getObjective()); assertEquals(null, campaigns.get(0).getSpendCap()); assertEquals("602123456789", campaigns.get(1).getId()); assertEquals("123456789", campaigns.get(1).getAccountId()); assertEquals(BuyingType.FIXED_CPM, campaigns.get(1).getBuyingType()); - assertEquals(CampaignStatus.PAUSED, campaigns.get(1).getCampaignStatus()); + assertEquals(CampaignStatus.PAUSED, campaigns.get(1).getStatus()); assertEquals("Campaign #2", campaigns.get(1).getName()); assertEquals(CampaignObjective.NONE, campaigns.get(1).getObjective()); assertEquals(null, campaigns.get(1).getSpendCap()); assertEquals("603123456789", campaigns.get(2).getId()); assertEquals("123456789", campaigns.get(2).getAccountId()); assertEquals(BuyingType.RESERVED, campaigns.get(2).getBuyingType()); - assertEquals(CampaignStatus.ARCHIVED, campaigns.get(2).getCampaignStatus()); + assertEquals(CampaignStatus.ARCHIVED, campaigns.get(2).getStatus()); assertEquals("Campaign #3", campaigns.get(2).getName()); assertEquals(CampaignObjective.WEBSITE_CONVERSIONS, campaigns.get(2).getObjective()); assertEquals("50000", campaigns.get(2).getSpendCap()); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java index 7469da62b..5a3909d5a 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java @@ -32,7 +32,7 @@ public void getCampaign() throws Exception { assertEquals("600123456789", campaign.getId()); assertEquals("123456789", campaign.getAccountId()); assertEquals(BuyingType.AUCTION, campaign.getBuyingType()); - assertEquals(CampaignStatus.ACTIVE, campaign.getCampaignStatus()); + assertEquals(CampaignStatus.ACTIVE, campaign.getStatus()); assertEquals("The test campaign name", campaign.getName()); assertEquals(CampaignObjective.POST_ENGAGEMENT, campaign.getObjective()); assertEquals("1000", campaign.getSpendCap()); @@ -49,7 +49,7 @@ public void getCampaign_withUnknownEnums() throws Exception { assertEquals("600123456789", campaign.getId()); assertEquals("123456789", campaign.getAccountId()); assertEquals(BuyingType.UNKNOWN, campaign.getBuyingType()); - assertEquals(CampaignStatus.UNKNOWN, campaign.getCampaignStatus()); + assertEquals(CampaignStatus.UNKNOWN, campaign.getStatus()); assertEquals("The test campaign name", campaign.getName()); assertEquals(CampaignObjective.UNKNOWN, campaign.getObjective()); @@ -154,7 +154,7 @@ public void updateAdCampaignName_unauthorized() throws Exception { @Test public void updateAdCampaignStatus() throws Exception { - String requestBody = "status=ACTIVE"; + String requestBody = "campaign_group_status=ACTIVE"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) From e34434278c61c04842b7c929f892cad583f69277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Wed, 6 May 2015 12:08:57 +0200 Subject: [PATCH 09/28] Clarifies using ad account ID in createAdCampaign methods. --- .../facebook/api/CampaignOperations.java | 24 +++++++++---------- .../facebook/api/impl/CampaignTemplate.java | 12 +++++----- .../facebook/api/CampaignTemplateTest.java | 12 +++++----- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java index 73a4033a7..58bfe7473 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java @@ -32,37 +32,37 @@ public interface CampaignOperations { /** * Creates new campaign with given name and status. * - * @param accountId the id of the ad account - * @param name the name of new campaign - * @param status the status of the new campaign (ACTIVE or PAUSED) + * @param adAccountId the ID of the ad account, the string act_{ad_account_id} + * @param name the name of new campaign + * @param status the status of the new campaign (ACTIVE or PAUSED) * @return the id of the ad campaign created * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - String createAdCampaign(String accountId, String name, CampaignStatus status); + String createAdCampaign(String adAccountId, String name, CampaignStatus status); /** * Creates new campaign with given name, status and objective. * - * @param accountId the id of the ad account - * @param name the name of the new campaign - * @param status the status of the new campaign (ACTIVE or PAUSED) - * @param objective the objective of this ad campaign - * @param spendCap the spend cap for the campaign defined as value of cents in your currency, set to null to allow unlimited spend. A minimum value is $100 USD (or approximate local equivalent). + * @param adAccountId the ID of the ad account, the string act_{ad_account_id} + * @param name the name of the new campaign + * @param status the status of the new campaign (ACTIVE or PAUSED) + * @param objective the objective of this ad campaign + * @param spendCap the spend cap for the campaign defined as value of cents in your currency, set to null to allow unlimited spend. A minimum value is $100 USD (or approximate local equivalent). * @return the id of the ad campaign created * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap); + String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap); /** * Creates new campaign with given name, status, objective and spend cap. * - * @param accountId the id of the ad account + * @param adAccountId the ID of the ad account, the string act_{ad_account_id} * @param name the name of the new campaign * @param status the status of the new campaign (ACTIVE or PAUSED) * @param objective the objective of this ad campaign @@ -74,7 +74,7 @@ public interface CampaignOperations { * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType); + String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType); /** * Updates the name of the ad campaign. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java index c8bc0bc67..2c85c050a 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java @@ -30,15 +30,15 @@ public AdCampaign getAdCampaign(String id) { return graphApi.fetchObject(id, AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); } - public String createAdCampaign(String accountId, String name, CampaignStatus status) { - return createAdCampaign(accountId, name, status, null, null); + public String createAdCampaign(String adAccountId, String name, CampaignStatus status) { + return createAdCampaign(adAccountId, name, status, null, null); } - public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap) { - return createAdCampaign(accountId, name, status, objective, spendCap, null); + public String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap) { + return createAdCampaign(adAccountId, name, status, objective, spendCap, null); } - public String createAdCampaign(String accountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType) { + public String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType) { requireAuthorization(); MultiValueMap map = new LinkedMultiValueMap(); map.add("name", name); @@ -52,7 +52,7 @@ public String createAdCampaign(String accountId, String name, CampaignStatus sta if (buyingType != null) { map.add("buying_type", buyingType.name()); } - return graphApi.publish("act_" + accountId, "adcampaign_groups", map); + return graphApi.publish(adAccountId, "adcampaign_groups", map); } public void updateAdCampaignName(String campaignId, String name) { diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java index 5a3909d5a..950a15cc6 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java @@ -68,7 +68,7 @@ public void createCampaign_withNameAndStatus() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED)); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED)); mockServer.verify(); } @@ -83,7 +83,7 @@ public void createCampaign_withInvalidStatus() throws Exception { .andRespond(withBadRequest().body(jsonResource("error-invalid-update-campaign-status")).contentType(MediaType.APPLICATION_JSON)); try { - facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with invalid status", CampaignStatus.ARCHIVED); + facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with invalid status", CampaignStatus.ARCHIVED); fail(); } catch (InvalidCampaignStatusException e) { assertEquals("New campaigns need to be either active or paused.", e.getMessage()); @@ -99,7 +99,7 @@ public void createCampaign_withObjective() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with objective", + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with objective", CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, "50000")); mockServer.verify(); } @@ -112,7 +112,7 @@ public void createCampaign_withoutSpendCap() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with spend cap", + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with spend cap", CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, null)); mockServer.verify(); } @@ -125,14 +125,14 @@ public void createCampaign_withBuyingType() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", "Campaign with objective", + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with objective", CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, "50000", BuyingType.AUCTION)); mockServer.verify(); } @Test(expected = NotAuthorizedException.class) public void createCampaign_unauthorized() throws Exception { - unauthorizedFacebookAds.campaignOperations().createAdCampaign("123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED); + unauthorizedFacebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED); } @Test From 6ebdd76800d72a42560fcf3a8e09c37106df5161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Wed, 13 May 2015 12:34:25 +0200 Subject: [PATCH 10/28] Adds AdSet class. --- .../social/facebook/api/AdSet.java | 114 ++++++++++++++++++ .../social/facebook/api/AdSetOperations.java | 13 ++ .../facebook/api/impl/json/AdSetMixin.java | 102 ++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSet.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSetOperations.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdSetMixin.java diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSet.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSet.java new file mode 100644 index 000000000..d493d11b9 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSet.java @@ -0,0 +1,114 @@ +package org.springframework.social.facebook.api; + +import java.util.Date; +import java.util.List; + +/** + * Model class representing an ad set. + * + * @author Sebastian Górecki + */ +public class AdSet extends FacebookObject { + private String id; + private String accountId; + private String campaignId; + private String name; + private AdSetStatus status; + + private boolean autobid; + private Object bidInfo; + private BidType bidType; + + private int budgetRemaining; + private int dailyBudget; + private int lifetimeBudget; + + private List creativeSequence; + private Object promotedObject; + private Object targeting; + + private Date startTime; + private Date endTime; + private Date createdTime; + private Date updatedTime; + + public String getId() { + return id; + } + + public String getAccountId() { + return accountId; + } + + public String getCampaignId() { + return campaignId; + } + + public String getName() { + return name; + } + + public AdSetStatus getStatus() { + return status; + } + + public boolean isAutobid() { + return autobid; + } + + public Object getBidInfo() { + return bidInfo; + } + + public BidType getBidType() { + return bidType; + } + + public int getBudgetRemaining() { + return budgetRemaining; + } + + public int getDailyBudget() { + return dailyBudget; + } + + public int getLifetimeBudget() { + return lifetimeBudget; + } + + public List getCreativeSequence() { + return creativeSequence; + } + + public Object getPromotedObject() { + return promotedObject; + } + + public Object getTargeting() { + return targeting; + } + + public Date getStartTime() { + return startTime; + } + + public Date getEndTime() { + return endTime; + } + + public Date getCreatedTime() { + return createdTime; + } + + public Date getUpdatedTime() { + return updatedTime; + } + + public enum BidType { + CPM, CPC, MULTI_PREMIUM, ABSOLUTE_OCPM, CPA, UNKNOWN + } + + public enum AdSetStatus { + ACTIVE, PAUSED, ARCHIVED, DELETED, CAMPAIGN_GROUP_PAUSED, UNKNOWN + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSetOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSetOperations.java new file mode 100644 index 000000000..dabe307c9 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSetOperations.java @@ -0,0 +1,13 @@ +package org.springframework.social.facebook.api; + +/** + * Defines operations for working with Facebook Ad set object. + * + * @author Sebastian Górecki + */ +public interface AdSetOperations { + static final String[] AD_SET_FIELDS = { + "account_id", "bid_info", "bid_type" + }; + +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdSetMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdSetMixin.java new file mode 100644 index 000000000..c00f5d2b2 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdSetMixin.java @@ -0,0 +1,102 @@ +package org.springframework.social.facebook.api.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.social.facebook.api.AdSet; +import org.springframework.social.facebook.api.AdSet.AdSetStatus; +import org.springframework.social.facebook.api.AdSet.BidType; + +import java.io.IOException; +import java.util.Date; +import java.util.List; + +/** + * * Annotated mixin to add Jackson annotations to AdSet. + * + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract public class AdSetMixin extends FacebookObjectMixin { + @JsonProperty("id") + private String id; + + @JsonProperty("account_id") + private String accountId; + + @JsonProperty("campaign_group_id") + private String campaignId; + + @JsonProperty("name") + private String name; + + @JsonProperty("campaign_stats") + @JsonDeserialize(using = AdSetStatusDeserializer.class) + private AdSetStatus status; + + @JsonProperty("is_autobid") + private boolean autobid; + + @JsonProperty("bid_info") + private Object bidInfo; + + @JsonProperty("bid_type") + @JsonDeserialize(using = BidTypeDeserializer.class) + private BidType bidType; + + @JsonProperty("budget_remaining") + private int budgetRemaining; + + @JsonProperty("daily_budget") + private int dailyBudget; + + @JsonProperty("lifetime_budget") + private int lifetimeBudget; + + @JsonProperty("creative_sequence") + private List creativeSequence; + + @JsonProperty("promoted_object") + private Object promotedObject; + + @JsonProperty("targeting") + private Object targeting; + + @JsonProperty("start_time") + private Date startTime; + + @JsonProperty("end_time") + private Date endTime; + + @JsonProperty("created_time") + private Date createdTime; + + @JsonProperty("updated_time") + private Date updatedTime; + + private class AdSetStatusDeserializer extends JsonDeserializer { + @Override + public AdSetStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + return AdSetStatus.valueOf(jp.getValueAsString().toUpperCase()); + } catch (IllegalArgumentException e) { + return AdSetStatus.UNKNOWN; + } + } + } + + private class BidTypeDeserializer extends JsonDeserializer { + @Override + public BidType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + try { + return BidType.valueOf(jp.getValueAsString().toUpperCase()); + } catch (IllegalArgumentException e) { + return BidType.UNKNOWN; + } + } + } +} From 5fde3fd6bf11f8fcc38caff5b57a1bbcc7bb80d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Thu, 14 May 2015 13:17:35 +0200 Subject: [PATCH 11/28] Refactoring of AdAccount Operations. --- .../social/facebook/api/GraphApi.java | 11 +- .../api/{ => ads}/AccountOperations.java | 85 ++++----- .../facebook/api/{ => ads}/AdAccount.java | 12 +- .../api/{ => ads}/AdAccountGroup.java | 4 +- .../facebook/api/{ => ads}/AdCampaign.java | 4 +- .../facebook/api/{ => ads}/AdInsight.java | 2 +- .../api/{ => ads}/AdInsightAction.java | 2 +- .../social/facebook/api/{ => ads}/AdUser.java | 4 +- .../api/{ => ads}/CampaignOperations.java | 9 +- .../facebook/api/{ => ads}/FacebookAds.java | 4 +- .../api/ads/impl/AccountTemplate.java | 91 ++++++++++ .../api/{ => ads}/impl/CampaignTemplate.java | 13 +- .../{ => ads}/impl/FacebookAdsTemplate.java | 13 +- .../impl/json/AdAccountGroupMixin.java | 5 +- .../{ => ads}/impl/json/AdAccountMixin.java | 19 +- .../{ => ads}/impl/json/AdCampaignMixin.java | 9 +- .../impl/json/AdInsightActionMixin.java | 5 +- .../{ => ads}/impl/json/AdInsightMixin.java | 7 +- .../api/{ => ads}/impl/json/AdUserMixin.java | 9 +- .../api/impl/AbstractFacebookOperations.java | 8 +- .../facebook/api/impl/AccountTemplate.java | 106 ------------ .../facebook/api/impl/FacebookTemplate.java | 162 ++++++++---------- .../api/impl/json/FacebookModule.java | 2 + .../api/impl/json/FacebookObjectMixin.java | 2 +- .../{ => ads}/AbstractFacebookAdsApiTest.java | 4 +- .../api/{ => ads}/AccountTemplateTest.java | 159 +++++++++-------- .../api/{ => ads}/CampaignTemplateTest.java | 11 +- .../api/{ => ads}/ad-account-campaigns.json | 0 .../api/{ => ads}/ad-account-insights.json | 0 .../api/{ => ads}/ad-account-no-users.json | 0 .../ad-account-temporarily-unavailable.json | 0 .../ad-account-unknown-capabilities.json | 0 .../api/{ => ads}/ad-account-users.json | 0 .../api/{ => ads}/ad-account-with-agency.json | 0 .../{ => ads}/ad-account-with-few-users.json | 0 .../ad-account-with-funding-details.json | 0 .../ad-account-with-tos-accepted.json | 0 .../ad-account-without-permission.json | 0 .../facebook/api/{ => ads}/ad-account.json | 0 .../facebook/api/{ => ads}/ad-accounts.json | 0 .../ad-campaign-with-unknown-enums.json | 0 .../facebook/api/{ => ads}/ad-campaign.json | 0 .../error-invalid-update-campaign-status.json | 0 43 files changed, 395 insertions(+), 367 deletions(-) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AccountOperations.java (68%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdAccount.java (95%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdAccountGroup.java (74%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdCampaign.java (90%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdInsight.java (98%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdInsightAction.java (84%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdUser.java (85%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/CampaignOperations.java (94%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/FacebookAds.java (69%) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/CampaignTemplate.java (86%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/FacebookAdsTemplate.java (63%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/json/AdAccountGroupMixin.java (66%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/json/AdAccountMixin.java (90%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/json/AdCampaignMixin.java (86%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/json/AdInsightActionMixin.java (59%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/json/AdInsightMixin.java (92%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/json/AdUserMixin.java (89%) delete mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java rename spring-social-facebook/src/test/java/org/springframework/social/facebook/api/{ => ads}/AbstractFacebookAdsApiTest.java (91%) rename spring-social-facebook/src/test/java/org/springframework/social/facebook/api/{ => ads}/AccountTemplateTest.java (86%) rename spring-social-facebook/src/test/java/org/springframework/social/facebook/api/{ => ads}/CampaignTemplateTest.java (95%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-campaigns.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-insights.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-no-users.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-temporarily-unavailable.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-unknown-capabilities.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-users.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-with-agency.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-with-few-users.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-with-funding-details.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-with-tos-accepted.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account-without-permission.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-account.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-accounts.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-campaign-with-unknown-enums.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/ad-campaign.json (100%) rename spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/{ => ads}/error-invalid-update-campaign-status.json (100%) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/GraphApi.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/GraphApi.java index 7f717849a..8a723d443 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/GraphApi.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/GraphApi.java @@ -131,7 +131,16 @@ public interface GraphApi { * @param data the data to publish to the connection. */ void post(String objectId, String connectionName, MultiValueMap data); - + + /** + * Updates data of an object. + * Requires appropriate permission to update to the object connection. + * @param objectId the object ID to update. + * @param data the data to update in the object. + * @return true if update was successful + */ + boolean update(String objectId, MultiValueMap data); + /** * Deletes an object. * Requires appropriate permission to delete the object. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AccountOperations.java similarity index 68% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AccountOperations.java index e3e0a5db4..8e0543e6d 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AccountOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AccountOperations.java @@ -1,11 +1,10 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; import org.springframework.social.ApiException; import org.springframework.social.InsufficientPermissionException; import org.springframework.social.MissingAuthorizationException; -import org.springframework.social.facebook.api.AdUser.AdUserRole; - -import java.util.List; +import org.springframework.social.facebook.api.*; +import org.springframework.social.facebook.api.ads.AdUser.AdUserRole; /** * Defines operations for working with Facebook Marketing API Ad Account object. @@ -40,12 +39,12 @@ public interface AccountOperations { * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - List getAdAccounts(String userId); + PagedList getAdAccounts(String userId); /** * Get the ad account by given id. * - * @param accountId the id of an ad account + * @param accountId the ID of the ad account (account_id) * @return the {@link AdAccount} object * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. @@ -56,79 +55,69 @@ public interface AccountOperations { /** * Get all ad campaigns of an ad account. * - * @param id the id of an ad account + * @param accountId the ID of the ad account (account_id) * @return the list of {@link AdCampaign} objects * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - PagedList getAdAccountCampaigns(String id); + PagedList getAdAccountCampaigns(String accountId); /** * Get all users of the ad account. * - * @param accountId the id of an ad account + * @param accountId the ID of the ad account, the string act_{ad_account_id} * @return the list of {@link AdUser} objects * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - List getAdAccountUsers(String accountId); - -// problems with Facebook Marketing API -// /** -// * Add the user to the ad account. -// * -// * @param accountId the id of an ad account -// * @param userId the id of an user -// * @param role the role for the new user in ad account -// * @throws ApiException if there is an error while communicating with Facebook. -// * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. -// * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. -// */ -// void addUserToAdAccount(String accountId, String userId, AdUserRole role); -// -// /** -// * Remove user's access to an ad account. -// * -// * @param accountId the id of an ad account -// * @param userId the id of an user -// * @throws ApiException if there is an error while communicating with Facebook. -// * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. -// * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. -// */ -// void deleteUserFromAdAccount(String accountId, String userId); + PagedList getAdAccountUsers(String accountId); /** - * Get the insight for the ad account in aggregate. + * Add the user to the ad account. * - * @param accountId the id of an ad account. - * @return the {@link AdInsight} object + * @param accountId the ID of the ad account (account_id) + * @param userId the id of an user + * @param role the role for the new user in ad account * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. */ - AdInsight getAdAccountInsight(String accountId); + void addUserToAdAccount(String accountId, String userId, AdUserRole role); /** - * Updates the ad account name. + * Remove user's access to an ad account. * - * @param accountId the id of an ad account. - * @param name the new account name + * @param accountId the ID of the ad account (account_id) + * @param userId the id of an user * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - void updateAdAccountName(String accountId, String name); + void deleteUserFromAdAccount(String accountId, String userId); /** - * Updates the ad account spending cap. + * Get the insight for the ad account in aggregate. * - * @param accountId the id of an ad account. - * @param spendCap The total amount that account can spend. Value specified in basic unit of the currency, e.g. dollars for USD. + * @param accountId the ID of the ad account (account_id) + * @return the {@link AdInsight} object * @throws ApiException if there is an error while communicating with Facebook. - * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + */ + AdInsight getAdAccountInsight(String accountId); + + /** + * Updates the ad account with information given in adAccount object. + * Currently only updates on ad account name and spend_cap are supported. + * + * @param accountId the ID of the ad account (account_id) + * @param adAccount the ad account object containing updated account information + * @return true if the update succeeded + * @throws ApiException if there is an error while communicating with Facebook. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. */ - void updateAdAccountSpendCap(String accountId, int spendCap); + boolean updateAdAccount(String accountId, AdAccount adAccount); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccount.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccount.java similarity index 95% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccount.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccount.java index fb79b8651..abe221d29 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccount.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccount.java @@ -1,4 +1,6 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.facebook.api.FacebookObject; import java.util.Date; import java.util.List; @@ -145,6 +147,10 @@ public String getName() { return name; } + public void setName(String name) { + this.name = name; + } + public boolean isOffsitePixelsTOSAccepted() { return offsitePixelsTOSAccepted; } @@ -157,6 +163,10 @@ public String getSpendCap() { return spendCap; } + public void setSpendCap(String spendCap) { + this.spendCap = spendCap; + } + public int getTimezoneId() { return timezoneId; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccountGroup.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccountGroup.java similarity index 74% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccountGroup.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccountGroup.java index 0a0eaa696..6b958f935 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdAccountGroup.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccountGroup.java @@ -1,4 +1,6 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.facebook.api.FacebookObject; /** * Model class representing an ad account group. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java similarity index 90% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java index 08b35ef67..822ca3d30 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdCampaign.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java @@ -1,4 +1,6 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.facebook.api.FacebookObject; /** * Model class representing an ad campaign. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsight.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdInsight.java similarity index 98% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsight.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdInsight.java index e76479088..8828a3f66 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsight.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdInsight.java @@ -1,4 +1,4 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; import java.util.Date; import java.util.List; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsightAction.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdInsightAction.java similarity index 84% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsightAction.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdInsightAction.java index 12c68322d..ce2908799 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdInsightAction.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdInsightAction.java @@ -1,4 +1,4 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; /** * Model class representing an ad insight action. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdUser.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdUser.java similarity index 85% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdUser.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdUser.java index cc4a8e221..0386a6bfd 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdUser.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdUser.java @@ -1,4 +1,6 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.facebook.api.FacebookObject; import java.util.List; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java similarity index 94% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java index 58bfe7473..1d2ec00d3 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java @@ -1,11 +1,12 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; import org.springframework.social.ApiException; import org.springframework.social.InsufficientPermissionException; import org.springframework.social.MissingAuthorizationException; -import org.springframework.social.facebook.api.AdCampaign.BuyingType; -import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; -import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.InvalidCampaignStatusException; /** * Defines operations for working with Facebook Ad Campaign object. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java similarity index 69% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java index 5400b6ba3..757ff90a4 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookAds.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java @@ -1,6 +1,6 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; -import org.springframework.social.facebook.api.impl.FacebookAdsTemplate; +import org.springframework.social.facebook.api.ads.impl.FacebookAdsTemplate; /** * Interface specifying a basic set of operations for interacting with Facebook Marketing API. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java new file mode 100644 index 000000000..98fe9c684 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java @@ -0,0 +1,91 @@ +package org.springframework.social.facebook.api.ads.impl; + +import org.springframework.social.facebook.api.*; +import org.springframework.social.facebook.api.ads.*; +import org.springframework.social.facebook.api.ads.AdUser.AdUserRole; +import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +/** + * @author Sebastian Górecki + */ +public class AccountTemplate extends AbstractFacebookOperations implements AccountOperations { + + private final GraphApi graphApi; + + private final RestTemplate restTemplate; + + public AccountTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean isAuthorizedForUser) { + super(isAuthorizedForUser); + this.graphApi = graphApi; + this.restTemplate = restTemplate; + } + + public PagedList getAdAccounts(String userId) { + requireAuthorization(); + return graphApi.fetchConnections(userId, "adaccounts", AdAccount.class, AD_ACCOUNT_FIELDS); + } + + public AdAccount getAdAccount(String accountId) { + requireAuthorization(); + return graphApi.fetchObject(getAdAccountId(accountId), AdAccount.class, AD_ACCOUNT_FIELDS); + } + + public PagedList getAdAccountCampaigns(String accountId) { + return graphApi.fetchConnections(getAdAccountId(accountId), "adcampaign_groups", AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); + } + + public PagedList getAdAccountUsers(String accountId) { + requireAuthorization(); + return graphApi.fetchConnections(getAdAccountId(accountId), "users", AdUser.class); + } + + public void addUserToAdAccount(String accountId, String userId, AdUserRole role) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + map.set("uid", userId); + map.set("role", String.valueOf(serializeRole(role))); + graphApi.post(getAdAccountId(accountId) + "/users", "", map); + } + + public void deleteUserFromAdAccount(String accountId, String userId) { + requireAuthorization(); + graphApi.delete(getAdAccountId(accountId) + "/users/" + userId); + } + + public AdInsight getAdAccountInsight(String accountId) { + requireAuthorization(); + PagedList insights = graphApi.fetchConnections(getAdAccountId(accountId), "insights", AdInsight.class, AD_ACCOUNT_INSIGHT_FIELDS); + return insights.get(0); + } + + public boolean updateAdAccount(String accountId, AdAccount adAccount) { + requireAuthorization(); + MultiValueMap map = new LinkedMultiValueMap(); + if (adAccount.getName() != null) { + map.set("name", adAccount.getName()); + } + if (adAccount.getSpendCap() != null) { + map.set("spend_cap", adAccount.getSpendCap()); + } + return graphApi.update(getAdAccountId(accountId), map); + } + + private int serializeRole(AdUserRole role) { + switch (role) { + case ADMINISTRATOR: + return 1001; + case ADVERTISER: + return 1002; + case ANALYST: + return 1003; + case SALES: + return 1004; + case UNKNOWN: + default: + return 0; + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java similarity index 86% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java index 2c85c050a..57c2b75d8 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java @@ -1,11 +1,12 @@ -package org.springframework.social.facebook.api.impl; +package org.springframework.social.facebook.api.ads.impl; -import org.springframework.social.facebook.api.AdCampaign; -import org.springframework.social.facebook.api.AdCampaign.BuyingType; -import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; -import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; -import org.springframework.social.facebook.api.CampaignOperations; +import org.springframework.social.facebook.api.ads.AdCampaign; +import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.ads.CampaignOperations; import org.springframework.social.facebook.api.GraphApi; +import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java similarity index 63% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java index 6fa84e642..11434a41d 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookAdsTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java @@ -1,8 +1,11 @@ -package org.springframework.social.facebook.api.impl; - -import org.springframework.social.facebook.api.AccountOperations; -import org.springframework.social.facebook.api.CampaignOperations; -import org.springframework.social.facebook.api.FacebookAds; +package org.springframework.social.facebook.api.ads.impl; + +import org.springframework.social.facebook.api.ads.AccountOperations; +import org.springframework.social.facebook.api.ads.CampaignOperations; +import org.springframework.social.facebook.api.ads.FacebookAds; +import org.springframework.social.facebook.api.ads.impl.AccountTemplate; +import org.springframework.social.facebook.api.ads.impl.CampaignTemplate; +import org.springframework.social.facebook.api.impl.FacebookTemplate; /** * This is the central class for interacting with Facebook Marketing API. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountGroupMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountGroupMixin.java similarity index 66% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountGroupMixin.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountGroupMixin.java index ea93e974f..f8ab2a63d 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountGroupMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountGroupMixin.java @@ -1,14 +1,15 @@ -package org.springframework.social.facebook.api.impl.json; +package org.springframework.social.facebook.api.ads.impl.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; /** * Annotated mixin to add Jackson annotations to AdAccountGroup. * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) -abstract class AdAccountGroupMixin extends FacebookObjectMixin { +public abstract class AdAccountGroupMixin extends FacebookObjectMixin { @JsonProperty("account_group_id") private String id; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java similarity index 90% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountMixin.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java index 6b9517608..4e57223c3 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdAccountMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java @@ -1,4 +1,4 @@ -package org.springframework.social.facebook.api.impl.json; +package org.springframework.social.facebook.api.ads.impl.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -11,13 +11,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.springframework.social.facebook.api.AdAccount; -import org.springframework.social.facebook.api.AdAccount.AccountStatus; -import org.springframework.social.facebook.api.AdAccount.AgencyClientDeclaration; -import org.springframework.social.facebook.api.AdAccount.Capabilities; -import org.springframework.social.facebook.api.AdAccount.TaxStatus; -import org.springframework.social.facebook.api.AdAccountGroup; -import org.springframework.social.facebook.api.AdUser; +import org.springframework.social.facebook.api.ads.AdAccount.AccountStatus; +import org.springframework.social.facebook.api.ads.AdAccount.AgencyClientDeclaration; +import org.springframework.social.facebook.api.ads.AdAccount.Capabilities; +import org.springframework.social.facebook.api.ads.AdAccount.TaxStatus; +import org.springframework.social.facebook.api.ads.AdAccountGroup; +import org.springframework.social.facebook.api.ads.AdUser; +import org.springframework.social.facebook.api.impl.json.FacebookModule; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; import java.io.IOException; import java.util.*; @@ -28,7 +29,7 @@ * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) -abstract class AdAccountMixin extends FacebookObjectMixin { +public abstract class AdAccountMixin extends FacebookObjectMixin { @JsonProperty("id") String id; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java similarity index 86% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java index 9bee2b00b..e1af9ae40 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdCampaignMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java @@ -1,4 +1,4 @@ -package org.springframework.social.facebook.api.impl.json; +package org.springframework.social.facebook.api.ads.impl.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -7,9 +7,10 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.springframework.social.facebook.api.AdCampaign.BuyingType; -import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; -import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; import java.io.IOException; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightActionMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightActionMixin.java similarity index 59% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightActionMixin.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightActionMixin.java index 1c2fe6ce8..c97829000 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightActionMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightActionMixin.java @@ -1,13 +1,14 @@ -package org.springframework.social.facebook.api.impl.json; +package org.springframework.social.facebook.api.ads.impl.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; /** * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) -abstract class AdInsightActionMixin extends FacebookObjectMixin { +public abstract class AdInsightActionMixin extends FacebookObjectMixin { @JsonProperty("action_type") private String actionType; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightMixin.java similarity index 92% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightMixin.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightMixin.java index 75eade6ec..3ff59fc4c 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdInsightMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightMixin.java @@ -1,8 +1,9 @@ -package org.springframework.social.facebook.api.impl.json; +package org.springframework.social.facebook.api.ads.impl.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import org.springframework.social.facebook.api.AdInsightAction; +import org.springframework.social.facebook.api.ads.AdInsightAction; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; import java.util.Date; import java.util.List; @@ -12,7 +13,7 @@ * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) -abstract class AdInsightMixin extends FacebookObjectMixin { +public abstract class AdInsightMixin extends FacebookObjectMixin { // id fields @JsonProperty("account_id") private String accountId; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdUserMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdUserMixin.java similarity index 89% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdUserMixin.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdUserMixin.java index 14c5f9a3b..32b6b20d2 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdUserMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdUserMixin.java @@ -1,4 +1,4 @@ -package org.springframework.social.facebook.api.impl.json; +package org.springframework.social.facebook.api.ads.impl.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -8,8 +8,9 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.springframework.social.facebook.api.AdUser.AdUserPermission; -import org.springframework.social.facebook.api.AdUser.AdUserRole; +import org.springframework.social.facebook.api.ads.AdUser.AdUserPermission; +import org.springframework.social.facebook.api.ads.AdUser.AdUserRole; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; import java.io.IOException; import java.util.ArrayList; @@ -21,7 +22,7 @@ * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) -abstract class AdUserMixin extends FacebookObjectMixin { +public abstract class AdUserMixin extends FacebookObjectMixin { @JsonProperty("id") private String id; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java index e2d7816e6..1c018f2c0 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java @@ -17,14 +17,18 @@ import org.springframework.social.MissingAuthorizationException; -class AbstractFacebookOperations { +public class AbstractFacebookOperations { private final boolean isAuthorized; public AbstractFacebookOperations(boolean isAuthorized) { this.isAuthorized = isAuthorized; } - + + public String getAdAccountId(String id) { + return "act_" + id; + } + protected void requireAuthorization() { if (!isAuthorized) { throw new MissingAuthorizationException("facebook"); diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java deleted file mode 100644 index 62a7fb211..000000000 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AccountTemplate.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.springframework.social.facebook.api.impl; - -import org.springframework.social.facebook.api.*; -import org.springframework.social.facebook.api.AdUser.AdUserRole; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.util.List; - -/** - * @author Sebastian Górecki - */ -public class AccountTemplate extends AbstractFacebookOperations implements AccountOperations { - - private final GraphApi graphApi; - - private final RestTemplate restTemplate; - - public AccountTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean isAuthorizedForUser) { - super(isAuthorizedForUser); - this.graphApi = graphApi; - this.restTemplate = restTemplate; - } - - public List getAdAccounts(String userId) { - requireAuthorization(); - return graphApi.fetchConnections(userId, "adaccounts", AdAccount.class, AD_ACCOUNT_FIELDS); - } - - public AdAccount getAdAccount(String id) { - requireAuthorization(); - return graphApi.fetchObject(id, AdAccount.class, AD_ACCOUNT_FIELDS); - } - - public PagedList getAdAccountCampaigns(String id) { - return graphApi.fetchConnections(id, "adcampaign_groups", AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); - } - - public List getAdAccountUsers(String accountId) { - requireAuthorization(); - return graphApi.fetchConnections(accountId, "users", AdUser.class); - } - -// problem with Facebook Marketing API -// public void addUserToAdAccount(String accountId, String userId, AdUserRole role) { -// requireAuthorization(); -// MultiValueMap map = new LinkedMultiValueMap(); -// map.set("uid", userId); -// map.set("role", String.valueOf(serializeRole(role))); -// graphApi.post(accountId + "/users", "", map); -// } -// -// public void deleteUserFromAdAccount(String accountId, String userId) { -// requireAuthorization(); -// graphApi.delete(accountId + "/users/" + userId); -// } -// - public AdInsight getAdAccountInsight(String accountId) { - requireAuthorization(); - PagedList insights = graphApi.fetchConnections(accountId, "insights", AdInsight.class, AD_ACCOUNT_INSIGHT_FIELDS); - return insights.get(0); - } - - public void updateAdAccountName(String accountId, String name) { - requireAuthorization(); - MultiValueMap map = new LinkedMultiValueMap(); - map.set("name", name); - graphApi.post(accountId, "", map); - } - - public void updateAdAccountSpendCap(String accountId, int spendCap) { - requireAuthorization(); - MultiValueMap map = new LinkedMultiValueMap(); - map.set("spend_cap", String.valueOf(spendCap)); - graphApi.post(accountId, "", map); - } - - private int serializeRole(AdUserRole role) { - switch (role) { - case ADMINISTRATOR: - return 1001; - case ADVERTISER: - return 1002; - case ANALYST: - return 1003; - case SALES: - return 1004; - case UNKNOWN: - default: - return 0; - } - } - - private String join(String[] strings) { - StringBuilder builder = new StringBuilder(); - if (strings.length > 0) { - builder.append(strings[0]); - for (int i = 1; i < strings.length; i++) { - builder.append("," + strings[i]); - } - } - return builder.toString(); - } - -} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java index 3cca6c86c..8bf7eea11 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java @@ -15,39 +15,16 @@ */ package org.springframework.social.facebook.api.impl; -import static org.springframework.social.facebook.api.impl.PagedListUtils.*; - -import java.io.IOException; -import java.net.URI; -import java.util.List; -import java.util.Map; - -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.springframework.http.*; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.social.NotAuthorizedException; import org.springframework.social.UncategorizedApiException; -import org.springframework.social.facebook.api.AchievementOperations; -import org.springframework.social.facebook.api.CommentOperations; -import org.springframework.social.facebook.api.EventOperations; -import org.springframework.social.facebook.api.Facebook; -import org.springframework.social.facebook.api.FeedOperations; -import org.springframework.social.facebook.api.FriendOperations; -import org.springframework.social.facebook.api.GroupOperations; -import org.springframework.social.facebook.api.ImageType; -import org.springframework.social.facebook.api.LikeOperations; -import org.springframework.social.facebook.api.MediaOperations; -import org.springframework.social.facebook.api.OpenGraphOperations; -import org.springframework.social.facebook.api.PageOperations; -import org.springframework.social.facebook.api.PagedList; -import org.springframework.social.facebook.api.PagingParameters; -import org.springframework.social.facebook.api.SocialContextOperations; -import org.springframework.social.facebook.api.TestUserOperations; -import org.springframework.social.facebook.api.UserOperations; +import org.springframework.social.facebook.api.*; import org.springframework.social.facebook.api.impl.json.FacebookModule; import org.springframework.social.oauth2.AbstractOAuth2ApiBinding; import org.springframework.social.oauth2.OAuth2Version; @@ -58,10 +35,12 @@ import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.CollectionType; -import com.fasterxml.jackson.databind.type.TypeFactory; +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +import static org.springframework.social.facebook.api.impl.PagedListUtils.getPagedListParameters; /** *

This is the central class for interacting with Facebook.

@@ -72,38 +51,39 @@ * Attempts to perform secured operations through such an instance, however, * will result in {@link NotAuthorizedException} being thrown. *

+ * * @author Craig Walls */ public class FacebookTemplate extends AbstractOAuth2ApiBinding implements Facebook { private String appId; - + private AchievementOperations achievementOperations; - + private UserOperations userOperations; - + private FriendOperations friendOperations; - + private FeedOperations feedOperations; - + private GroupOperations groupOperations; - + private CommentOperations commentOperations; - + private LikeOperations likeOperations; - + private EventOperations eventOperations; - + private MediaOperations mediaOperations; - + private PageOperations pageOperations; - + private OpenGraphOperations openGraphOperations; - + private SocialContextOperations socialContextOperations; - + private TestUserOperations testUserOperations; - + private ObjectMapper objectMapper; private String applicationNamespace; @@ -111,6 +91,7 @@ public class FacebookTemplate extends AbstractOAuth2ApiBinding implements Facebo /** * Create a new instance of FacebookTemplate. * This constructor creates the FacebookTemplate using a given access token. + * * @param accessToken An access token given by Facebook after a successful OAuth 2 authentication (or through Facebook's JS library). */ public FacebookTemplate(String accessToken) { @@ -120,14 +101,14 @@ public FacebookTemplate(String accessToken) { public FacebookTemplate(String accessToken, String applicationNamespace) { this(accessToken, applicationNamespace, null); } - + public FacebookTemplate(String accessToken, String applicationNamespace, String appId) { super(accessToken); this.applicationNamespace = applicationNamespace; this.appId = appId; initialize(); } - + @Override public void setRequestFactory(ClientHttpRequestFactory requestFactory) { // Wrap the request factory with a BufferingClientHttpRequestFactory so that the error handler can do repeat reads on the response.getBody() @@ -137,11 +118,11 @@ public void setRequestFactory(ClientHttpRequestFactory requestFactory) { public AchievementOperations achievementOperations() { return achievementOperations; } - + public UserOperations userOperations() { return userOperations; } - + public LikeOperations likeOperations() { return likeOperations; } @@ -149,11 +130,11 @@ public LikeOperations likeOperations() { public FriendOperations friendOperations() { return friendOperations; } - + public FeedOperations feedOperations() { return feedOperations; } - + public GroupOperations groupOperations() { return groupOperations; } @@ -161,39 +142,39 @@ public GroupOperations groupOperations() { public CommentOperations commentOperations() { return commentOperations; } - + public EventOperations eventOperations() { return eventOperations; } - + public MediaOperations mediaOperations() { return mediaOperations; } - + public PageOperations pageOperations() { return pageOperations; } - + public RestOperations restOperations() { return getRestTemplate(); } - + public OpenGraphOperations openGraphOperations() { return openGraphOperations; } - + public SocialContextOperations socialContextOperations() { return socialContextOperations; } - + public String getApplicationNamespace() { return applicationNamespace; } - + public TestUserOperations testUserOperations() { return testUserOperations; } - + // low-level Graph API operations public T fetchObject(String objectId, Class type) { URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId).build(); @@ -202,10 +183,10 @@ public T fetchObject(String objectId, Class type) { public T fetchObject(String objectId, Class type, String... fields) { MultiValueMap queryParameters = new LinkedMultiValueMap(); - if(fields.length > 0) { + if (fields.length > 0) { String joinedFields = join(fields); queryParameters.set("fields", joinedFields); - } + } return fetchObject(objectId, type, queryParameters); } @@ -216,10 +197,10 @@ public T fetchObject(String objectId, Class type, MultiValueMap PagedList fetchConnections(String objectId, String connectionType, Class type, String... fields) { MultiValueMap queryParameters = new LinkedMultiValueMap(); - if(fields.length > 0) { + if (fields.length > 0) { String joinedFields = join(fields); queryParameters.set("fields", joinedFields); - } + } return fetchConnections(objectId, connectionType, type, queryParameters); } @@ -238,23 +219,23 @@ public PagedList fetchPagedConnections(String objectId, String connection } public PagedList fetchConnections(String objectId, String connectionType, Class type, MultiValueMap queryParameters, String... fields) { - if(fields.length > 0) { + if (fields.length > 0) { String joinedFields = join(fields); queryParameters.set("fields", joinedFields); } return fetchPagedConnections(objectId, connectionType, type, queryParameters); } - + private PagedList pagify(Class type, JsonNode jsonNode) { List data = deserializeDataList(jsonNode.get("data"), type); if (!jsonNode.has("paging")) { return new PagedList(data, null, null); } - + JsonNode pagingNode = jsonNode.get("paging"); PagingParameters previousPage = getPagedListParameters(pagingNode, "previous"); PagingParameters nextPage = getPagedListParameters(pagingNode, "next"); - + Integer totalCount = null; if (jsonNode.has("summary")) { JsonNode summaryNode = jsonNode.get("summary"); @@ -262,20 +243,20 @@ private PagedList pagify(Class type, JsonNode jsonNode) { totalCount = summaryNode.get("total_count").intValue(); } } - + return new PagedList(data, previousPage, nextPage, totalCount); } public byte[] fetchImage(String objectId, String connectionType, ImageType type) { URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId + "/" + connectionType + "?type=" + type.toString().toLowerCase()).build(); ResponseEntity response = getRestTemplate().getForEntity(uri, byte[].class); - if(response.getStatusCode() == HttpStatus.FOUND) { + if (response.getStatusCode() == HttpStatus.FOUND) { throw new UnsupportedOperationException("Attempt to fetch image resulted in a redirect which could not be followed. Add Apache HttpComponents HttpClient to the classpath " + "to be able to follow redirects."); } return response.getBody(); } - + @SuppressWarnings("unchecked") public String publish(String objectId, String connectionType, MultiValueMap data) { MultiValueMap requestData = new LinkedMultiValueMap(data); @@ -283,38 +264,45 @@ public String publish(String objectId, String connectionType, MultiValueMap response = getRestTemplate().postForObject(uri, requestData, Map.class); return (String) response.get("id"); } - + public void post(String objectId, MultiValueMap data) { post(objectId, null, data); } - + public void post(String objectId, String connectionType, MultiValueMap data) { String connectionPath = connectionType != null ? "/" + connectionType : ""; URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId + connectionPath).build(); getRestTemplate().postForObject(uri, new LinkedMultiValueMap(data), String.class); } - + + public boolean update(String objectId, MultiValueMap data) { + MultiValueMap requestData = new LinkedMultiValueMap(data); + URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId).build(); + Map response = getRestTemplate().postForObject(uri, requestData, Map.class); + return Boolean.valueOf(response.get("success")); + } + public void delete(String objectId) { LinkedMultiValueMap deleteRequest = new LinkedMultiValueMap(); deleteRequest.set("method", "delete"); URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId).build(); getRestTemplate().postForObject(uri, deleteRequest, String.class); } - + public void delete(String objectId, String connectionType) { LinkedMultiValueMap deleteRequest = new LinkedMultiValueMap(); deleteRequest.set("method", "delete"); URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId + "/" + connectionType).build(); getRestTemplate().postForObject(uri, deleteRequest, String.class); } - + public void delete(String objectId, String connectionType, MultiValueMap data) { data.set("method", "delete"); URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId + "/" + connectionType).build(); HttpEntity> entity = new HttpEntity>(data, new HttpHeaders()); getRestTemplate().exchange(uri, HttpMethod.POST, entity, String.class); } - + // AbstractOAuth2ApiBinding hooks @Override protected OAuth2Version getOAuth2Version() { @@ -329,19 +317,19 @@ protected void configureRestTemplate(RestTemplate restTemplate) { @Override protected MappingJackson2HttpMessageConverter getJsonMessageConverter() { MappingJackson2HttpMessageConverter converter = super.getJsonMessageConverter(); - objectMapper = new ObjectMapper(); + objectMapper = new ObjectMapper(); objectMapper.registerModule(new FacebookModule()); - converter.setObjectMapper(objectMapper); + converter.setObjectMapper(objectMapper); return converter; } - + // private helpers private void initialize() { // Wrap the request factory with a BufferingClientHttpRequestFactory so that the error handler can do repeat reads on the response.getBody() super.setRequestFactory(ClientHttpRequestFactorySelector.bufferRequests(getRestTemplate().getRequestFactory())); initSubApis(); } - + private void initSubApis() { achievementOperations = new AchievementTemplate(this); openGraphOperations = new OpenGraphTemplate(this); @@ -357,7 +345,7 @@ private void initSubApis() { testUserOperations = new TestUserTemplate(getRestTemplate(), appId); socialContextOperations = new SocialContextTemplate(getRestTemplate()); } - + @SuppressWarnings("unchecked") private List deserializeDataList(JsonNode jsonNode, final Class elementType) { try { @@ -367,10 +355,10 @@ private List deserializeDataList(JsonNode jsonNode, final Class elemen throw new UncategorizedApiException("facebook", "Error deserializing data from Facebook: " + e.getMessage(), e); } } - + private String join(String[] strings) { StringBuilder builder = new StringBuilder(); - if(strings.length > 0) { + if (strings.length > 0) { builder.append(strings[0]); for (int i = 1; i < strings.length; i++) { builder.append("," + strings[i]); @@ -378,5 +366,5 @@ private String join(String[] strings) { } return builder.toString(); } - + } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java index 5deb68094..fc62dd5ce 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java @@ -19,6 +19,8 @@ import org.springframework.social.facebook.api.Photo.Image; import org.springframework.social.facebook.api.Video.VideoFormat; import org.springframework.social.facebook.api.WorkEntry.Project; +import org.springframework.social.facebook.api.ads.*; +import org.springframework.social.facebook.api.ads.impl.json.*; import org.springframework.social.facebook.api.impl.json.PhotoMixin.ImageMixin; import org.springframework.social.facebook.api.impl.json.VideoMixin.VideoFormatMixin; import org.springframework.social.facebook.api.impl.json.WorkEntryMixin.ProjectMixin; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java index 823687680..2e1b8e431 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java @@ -23,7 +23,7 @@ * @author Craig Walls */ @JsonIgnoreProperties(ignoreUnknown = true) -abstract class FacebookObjectMixin { +public abstract class FacebookObjectMixin { @JsonAnySetter abstract void add(String key, Object value); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AbstractFacebookAdsApiTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AbstractFacebookAdsApiTest.java similarity index 91% rename from spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AbstractFacebookAdsApiTest.java rename to spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AbstractFacebookAdsApiTest.java index e41c2d211..37ec63c42 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AbstractFacebookAdsApiTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AbstractFacebookAdsApiTest.java @@ -1,9 +1,9 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; import org.junit.Before; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.social.facebook.api.impl.FacebookAdsTemplate; +import org.springframework.social.facebook.api.ads.impl.FacebookAdsTemplate; import org.springframework.test.web.client.MockRestServiceServer; import java.text.DateFormat; diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java similarity index 86% rename from spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java rename to spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java index 76062483b..b68645419 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/AccountTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java @@ -1,15 +1,17 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.social.NotAuthorizedException; -import org.springframework.social.facebook.api.AdAccount.Capabilities; -import org.springframework.social.facebook.api.AdAccount.TaxStatus; -import org.springframework.social.facebook.api.AdUser.AdUserPermission; -import org.springframework.social.facebook.api.AdUser.AdUserRole; -import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; -import org.springframework.social.facebook.api.AdCampaign.BuyingType; -import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.PagedList; +import org.springframework.social.facebook.api.ads.*; +import org.springframework.social.facebook.api.ads.AdAccount.Capabilities; +import org.springframework.social.facebook.api.ads.AdAccount.TaxStatus; +import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.ads.AdUser.AdUserPermission; +import org.springframework.social.facebook.api.ads.AdUser.AdUserRole; import java.util.List; @@ -56,7 +58,7 @@ public void getAdAccount() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertEquals(AdAccount.AccountStatus.ACTIVE, adAccount.getStatus()); assertEquals(1, adAccount.getUsers().size()); @@ -74,7 +76,7 @@ public void getAdAccount_withStatusTemporarilyUnavailable() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-temporarily-unavailable"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertEquals(AdAccount.AccountStatus.TEMPORARILY_UNAVAILABLE, adAccount.getStatus()); assertEquals(1, adAccount.getUsers().size()); @@ -91,7 +93,7 @@ public void getAdAccount_withUnknownCapabilities() throws Exception { .andExpect(method(GET)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-unknown-capabilities"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertEquals("act_123456789", adAccount.getId()); assertEquals(123456789, adAccount.getAccountId()); assertEquals(4, adAccount.getCapabilities().size()); @@ -108,7 +110,7 @@ public void getAdAccount_withEmptyUsers() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-no-users"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertEquals(0, adAccount.getUsers().size()); } @@ -120,7 +122,7 @@ public void getAdAccount_withFewUsers() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-with-few-users"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertEquals(2, adAccount.getUsers().size()); assertEquals("1234", adAccount.getUsers().get(0).getId()); @@ -141,7 +143,7 @@ public void getAdAccount_userWithNoPermissions() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-without-permission"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertEquals(1, adAccount.getUsers().size()); assertEquals("1234", adAccount.getUsers().get(0).getId()); @@ -156,7 +158,7 @@ public void getAdAccount_withAgencyClientDeclaration() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-with-agency"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertEquals(1, adAccount.getAgencyClientDeclaration().getAgencyRepresentingClient()); assertEquals(0, adAccount.getAgencyClientDeclaration().getClientBasedInFrance()); @@ -179,7 +181,7 @@ public void getAdAccount_withFundingDetails() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-with-funding-details"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertTrue(adAccount.getFundingSourceDetails().containsKey("id")); assertEquals("12345678987654321", adAccount.getFundingSourceDetails().get("id")); @@ -196,7 +198,7 @@ public void getAdAccount_withTosAccepted() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-with-tos-accepted"), MediaType.APPLICATION_JSON)); - AdAccount adAccount = facebookAds.accountOperations().getAdAccount("act_123456789"); + AdAccount adAccount = facebookAds.accountOperations().getAdAccount("123456789"); assertAdAccountFields(adAccount); assertTrue(adAccount.getTosAccepted().containsKey("206760949512025")); assertEquals((Integer) 1, adAccount.getTosAccepted().get("206760949512025")); @@ -206,7 +208,7 @@ public void getAdAccount_withTosAccepted() throws Exception { @Test(expected = NotAuthorizedException.class) public void getAdAccount_unauthorized() throws Exception { - unauthorizedFacebookAds.accountOperations().getAdAccount("act_123456789"); + unauthorizedFacebookAds.accountOperations().getAdAccount("123456789"); } @Test @@ -215,7 +217,7 @@ public void getAdAccountCampaigns() throws Exception { .andExpect(method(GET)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-campaigns"), MediaType.APPLICATION_JSON)); - PagedList campaigns = facebookAds.accountOperations().getAdAccountCampaigns("act_123456789"); + PagedList campaigns = facebookAds.accountOperations().getAdAccountCampaigns("123456789"); assertEquals(3, campaigns.size()); assertEquals("601123456789", campaigns.get(0).getId()); assertEquals("123456789", campaigns.get(0).getAccountId()); @@ -247,7 +249,7 @@ public void getAccountUsers() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-users"), MediaType.APPLICATION_JSON)); - List adAccountUsers = facebookAds.accountOperations().getAdAccountUsers("act_123456789"); + List adAccountUsers = facebookAds.accountOperations().getAdAccountUsers("123456789"); assertEquals(3, adAccountUsers.size()); assertEquals("123456789", adAccountUsers.get(0).getId()); assertEquals("Account #1", adAccountUsers.get(0).getName()); @@ -274,42 +276,42 @@ public void getAccountUsers() throws Exception { @Test(expected = NotAuthorizedException.class) public void getAccountUsers_unauthorized() throws Exception { - unauthorizedFacebookAds.accountOperations().getAdAccountUsers("act_123456789"); + unauthorizedFacebookAds.accountOperations().getAdAccountUsers("123456789"); } -// @Test -// public void addUserToAccount() throws Exception { -// String requestBody = "uid=123456&role=1002"; -// mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/users/")) -// .andExpect(method(POST)) -// .andExpect(content().string(requestBody)) -// .andExpect(header("Authorization", "OAuth someAccessToken")) -// .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); -// facebookAds.accountOperations().addUserToAdAccount("act_123456789", "123456", AdUserRole.ADVERTISER); -// mockServer.verify(); -// } -// -// @Test(expected = NotAuthorizedException.class) -// public void addUserToAccount_unauthorized() throws Exception { -// unauthorizedFacebookAds.accountOperations().addUserToAdAccount("act_123456789", "123456", AdUserRole.ADVERTISER); -// } -// -// @Test -// public void deleteUserFromAccount() throws Exception { -// String requestBody = "method=delete"; -// mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/users/123456")) -// .andExpect(method(POST)) -// .andExpect(header("Authorization", "OAuth someAccessToken")) -// .andExpect(content().string(requestBody)) -// .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); -// facebookAds.accountOperations().deleteUserFromAdAccount("act_123456789", "123456"); -// mockServer.verify(); -// } -// -// @Test(expected = NotAuthorizedException.class) -// public void deleteUserFromAccount_unauthorized() throws Exception { -// unauthorizedFacebookAds.accountOperations().deleteUserFromAdAccount("act_123456789", "123456"); -// } + @Test + public void addUserToAccount() throws Exception { + String requestBody = "uid=123456&role=1002"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/users/")) + .andExpect(method(POST)) + .andExpect(content().string(requestBody)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.accountOperations().addUserToAdAccount("123456789", "123456", AdUserRole.ADVERTISER); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void addUserToAccount_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().addUserToAdAccount("123456789", "123456", AdUserRole.ADVERTISER); + } + + @Test + public void deleteUserFromAccount() throws Exception { + String requestBody = "method=delete"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/users/123456")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.accountOperations().deleteUserFromAdAccount("123456789", "123456"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void deleteUserFromAccount_unauthorized() throws Exception { + unauthorizedFacebookAds.accountOperations().deleteUserFromAdAccount("123456789", "123456"); + } @Test public void getAccountInsight() throws Exception { @@ -318,7 +320,7 @@ public void getAccountInsight() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-account-insights"), MediaType.APPLICATION_JSON)); - AdInsight insight = facebookAds.accountOperations().getAdAccountInsight("act_123456789"); + AdInsight insight = facebookAds.accountOperations().getAdAccountInsight("123456789"); assertEquals("123456789", insight.getAccountId()); assertEquals("Account Test Name #1", insight.getAccountName()); assertEquals(0.016042780748663, insight.getActionsPerImpression(), EPSILON); @@ -383,41 +385,60 @@ public void getAccountInsight() throws Exception { @Test(expected = NotAuthorizedException.class) public void getAccountInsight_unauthorized() throws Exception { - unauthorizedFacebookAds.accountOperations().getAdAccountInsight("act_123456789"); + unauthorizedFacebookAds.accountOperations().getAdAccountInsight("123456789"); } @Test - public void updateAccountName() throws Exception { - String requestBody = "name=New+account+name"; - mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/")) + public void updateAdAccount_nameOnly() throws Exception { + String requestBody = "name=New+Test+Name"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); - facebookAds.accountOperations().updateAdAccountName("act_123456789", "New account name"); + AdAccount adAccount = new AdAccount(); + adAccount.setName("New Test Name"); + boolean updateStatus = facebookAds.accountOperations().updateAdAccount("123456789", adAccount); + assertTrue(updateStatus); mockServer.verify(); } - @Test(expected = NotAuthorizedException.class) - public void updateAccountName_unauthorized() throws Exception { - unauthorizedFacebookAds.accountOperations().updateAdAccountName("act_123456789", "New account name"); + @Test + public void updateAdAccount_spendCapOnly() throws Exception { + String requestBody = "spend_cap=10000"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + AdAccount adAccount = new AdAccount(); + adAccount.setSpendCap("10000"); + boolean updateStatus = facebookAds.accountOperations().updateAdAccount("123456789", adAccount); + assertTrue(updateStatus); + mockServer.verify(); } @Test - public void updateAccountSpendCap() throws Exception { - String requestBody = "spend_cap=100"; - mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/")) + public void updateAdAccount_bothNameAndSpendCap() throws Exception { + String requestBody = "name=Super+cool+name&spend_cap=11111"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); - facebookAds.accountOperations().updateAdAccountSpendCap("act_123456789", 100); + AdAccount adAccount = new AdAccount(); + adAccount.setName("Super cool name"); + adAccount.setSpendCap("11111"); + boolean updateStatus = facebookAds.accountOperations().updateAdAccount("123456789", adAccount); + assertTrue(updateStatus); mockServer.verify(); } @Test(expected = NotAuthorizedException.class) - public void updateAccountSpendCap_unauthorized() throws Exception { - unauthorizedFacebookAds.accountOperations().updateAdAccountSpendCap("act_123456789", 100); + public void updateAdAccount_unauthorized() throws Exception { + AdAccount adAccount = new AdAccount(); + adAccount.setName("abc"); + unauthorizedFacebookAds.accountOperations().updateAdAccount("123456789", adAccount); } private void assertAdAccountsFields(List adAccounts) { diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java similarity index 95% rename from spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java rename to spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index 950a15cc6..a9ee6c68a 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -1,12 +1,15 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.social.NotAuthorizedException; -import org.springframework.social.facebook.api.AdCampaign.BuyingType; -import org.springframework.social.facebook.api.AdCampaign.CampaignObjective; -import org.springframework.social.facebook.api.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.InvalidCampaignStatusException; +import org.springframework.social.facebook.api.ads.AbstractFacebookAdsApiTest; +import org.springframework.social.facebook.api.ads.AdCampaign; +import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; +import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-campaigns.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-campaigns.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-campaigns.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-campaigns.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-insights.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-insights.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-insights.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-insights.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-no-users.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-no-users.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-no-users.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-no-users.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-temporarily-unavailable.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-temporarily-unavailable.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-temporarily-unavailable.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-temporarily-unavailable.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-unknown-capabilities.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-unknown-capabilities.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-unknown-capabilities.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-unknown-capabilities.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-users.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-users.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-users.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-users.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-agency.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-agency.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-agency.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-agency.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-few-users.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-few-users.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-few-users.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-few-users.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-funding-details.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-funding-details.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-funding-details.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-funding-details.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-tos-accepted.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-tos-accepted.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-with-tos-accepted.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-with-tos-accepted.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-without-permission.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-without-permission.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account-without-permission.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account-without-permission.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-account.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-account.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-accounts.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-accounts.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-accounts.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-accounts.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign-with-unknown-enums.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-with-unknown-enums.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign-with-unknown-enums.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-with-unknown-enums.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ad-campaign.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign.json diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-invalid-update-campaign-status.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/error-invalid-update-campaign-status.json similarity index 100% rename from spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-invalid-update-campaign-status.json rename to spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/error-invalid-update-campaign-status.json From 7afa9d8d2eb29eae8fa8e14c6c9963410ee0de41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Thu, 14 May 2015 15:17:15 +0200 Subject: [PATCH 12/28] Refactoring AdCampaing and CampaignOperations. --- .../social/facebook/api/ads/AdCampaign.java | 24 ++- .../facebook/api/ads/CampaignOperations.java | 95 ++------- .../api/ads/impl/CampaignTemplate.java | 81 ++++---- .../api/ads/impl/json/AdCampaignMixin.java | 2 +- .../facebook/api/ads/AccountTemplateTest.java | 6 +- .../api/ads/CampaignTemplateTest.java | 181 +++++++++++++----- 6 files changed, 212 insertions(+), 177 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java index 822ca3d30..4ea91aee0 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java @@ -14,7 +14,7 @@ public class AdCampaign extends FacebookObject { private CampaignStatus status; private String name; private CampaignObjective objective; - private String spendCap; + private int spendCap; public AdCampaign() { } @@ -23,10 +23,18 @@ public CampaignStatus getStatus() { return status; } + public void setStatus(CampaignStatus status) { + this.status = status; + } + public String getName() { return name; } + public void setName(String name) { + this.name = name; + } + public String getId() { return id; } @@ -39,14 +47,26 @@ public BuyingType getBuyingType() { return buyingType; } + public void setBuyingType(BuyingType buyingType) { + this.buyingType = buyingType; + } + public CampaignObjective getObjective() { return objective; } - public String getSpendCap() { + public void setObjective(CampaignObjective objective) { + this.objective = objective; + } + + public int getSpendCap() { return spendCap; } + public void setSpendCap(int spendCap) { + this.spendCap = spendCap; + } + public enum BuyingType { AUCTION, FIXED_CPM, RESERVED, MIXED, UNKNOWN } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java index 1d2ec00d3..55460d2f9 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java @@ -3,10 +3,8 @@ import org.springframework.social.ApiException; import org.springframework.social.InsufficientPermissionException; import org.springframework.social.MissingAuthorizationException; -import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; -import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; -import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; import org.springframework.social.facebook.api.InvalidCampaignStatusException; +import org.springframework.social.facebook.api.PagedList; /** * Defines operations for working with Facebook Ad Campaign object. @@ -20,106 +18,51 @@ public interface CampaignOperations { }; /** - * Get the campaign by given id. + * Gets all ad campaigns belonging to user. * - * @param id the id of the campaign - * @return the {@link AdCampaign} object. + * @param accountId the ID of the ad account (account_id) + * @return the list of {@link AdCampaign} objects * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - AdCampaign getAdCampaign(String id); + PagedList getAdCampaigns(String accountId); /** - * Creates new campaign with given name and status. - * - * @param adAccountId the ID of the ad account, the string act_{ad_account_id} - * @param name the name of new campaign - * @param status the status of the new campaign (ACTIVE or PAUSED) - * @return the id of the ad campaign created - * @throws ApiException if there is an error while communicating with Facebook. - * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. - * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign - */ - String createAdCampaign(String adAccountId, String name, CampaignStatus status); - - /** - * Creates new campaign with given name, status and objective. + * Get the campaign by given id. * - * @param adAccountId the ID of the ad account, the string act_{ad_account_id} - * @param name the name of the new campaign - * @param status the status of the new campaign (ACTIVE or PAUSED) - * @param objective the objective of this ad campaign - * @param spendCap the spend cap for the campaign defined as value of cents in your currency, set to null to allow unlimited spend. A minimum value is $100 USD (or approximate local equivalent). - * @return the id of the ad campaign created + * @param id the id of the campaign + * @return the {@link AdCampaign} object. * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap); + AdCampaign getAdCampaign(String id); /** - * Creates new campaign with given name, status, objective and spend cap. + * Creates new campaign based on adCampaign object. * - * @param adAccountId the ID of the ad account, the string act_{ad_account_id} - * @param name the name of the new campaign - * @param status the status of the new campaign (ACTIVE or PAUSED) - * @param objective the objective of this ad campaign - * @param spendCap the spend cap for the campaign defined as value of cents in your currency, set to null to allow unlimited spend. A minimum value is $100 USD (or approximate local equivalent). - * @param buyingType buying type - this field will help Facebook make future optimizations to delivery, pricing, and limits - * @return the id of the ad campaign created + * @param accountId the ID of the ad account (account_id) + * @param adCampaign the ad campaign object + * @return the id of the created ad campaign * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. * @throws InvalidCampaignStatusException if you provided wrong status for the new campaign */ - String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType); - - /** - * Updates the name of the ad campaign. - * - * @param campaignId the id of ad campaign - * @param name new name of the campaign - * @throws ApiException if there is an error while communicating with Facebook. - * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. - * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - */ - void updateAdCampaignName(String campaignId, String name); - - /** - * Updates the status of the ad campaign. - * - * @param campaignId the id of ad campaign - * @param status new status of the campaign - * @throws ApiException if there is an error while communicating with Facebook. - * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. - * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - */ - void updateAdCampaignStatus(String campaignId, CampaignStatus status); - - /** - * Updates the objective of the ad campaign. - * - * @param campaignId the id of ad campaign - * @param objective new objective of the campaign - * @throws ApiException if there is an error while communicating with Facebook. - * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. - * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - */ - void updateAdCampaignObjective(String campaignId, CampaignObjective objective); + String createAdCampaign(String accountId, AdCampaign adCampaign); /** - * Updates spend cap of the ad campaign. + * Updates the ad campaign with information in adCampaign object. * - * @param campaignId the id of ad campaign - * @param spendCap new spend cap + * @param campaignId the ID of the ad campaign to update + * @param adCampaign the ad campaign object + * @return true if update was successful * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - void updateAdCampaignSpendCap(String campaignId, int spendCap); + boolean updateAdCampaign(String campaignId, AdCampaign adCampaign); /** * Deletes the ad campaign given by id. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java index 57c2b75d8..0f18e930d 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java @@ -1,11 +1,10 @@ package org.springframework.social.facebook.api.ads.impl; +import org.springframework.social.facebook.api.GraphApi; +import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AdCampaign; -import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; -import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; import org.springframework.social.facebook.api.ads.CampaignOperations; -import org.springframework.social.facebook.api.GraphApi; import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -26,68 +25,52 @@ public CampaignTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean is this.restTemplate = restTemplate; } - public AdCampaign getAdCampaign(String id) { + public PagedList getAdCampaigns(String accountId) { requireAuthorization(); - return graphApi.fetchObject(id, AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); + return graphApi.fetchConnections(getAdAccountId(accountId), "adcampaign_groups", AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); } - public String createAdCampaign(String adAccountId, String name, CampaignStatus status) { - return createAdCampaign(adAccountId, name, status, null, null); - } - - public String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap) { - return createAdCampaign(adAccountId, name, status, objective, spendCap, null); - } - - public String createAdCampaign(String adAccountId, String name, CampaignStatus status, CampaignObjective objective, String spendCap, BuyingType buyingType) { - requireAuthorization(); - MultiValueMap map = new LinkedMultiValueMap(); - map.add("name", name); - map.add("campaign_group_status", status.name()); - if (objective != null) { - map.add("objective", objective.name()); - } - if (spendCap != null) { - map.add("spend_cap", spendCap); - } - if (buyingType != null) { - map.add("buying_type", buyingType.name()); - } - return graphApi.publish(adAccountId, "adcampaign_groups", map); - } - - public void updateAdCampaignName(String campaignId, String name) { + public AdCampaign getAdCampaign(String id) { requireAuthorization(); - MultiValueMap map = new LinkedMultiValueMap(); - map.add("name", name); - graphApi.post(campaignId, map); + return graphApi.fetchObject(id, AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); } - public void updateAdCampaignStatus(String campaignId, CampaignStatus status) { + public String createAdCampaign(String accountId, AdCampaign adCampaign) { requireAuthorization(); - MultiValueMap map = new LinkedMultiValueMap(); - map.add("campaign_group_status", status.name()); - graphApi.post(campaignId, map); + MultiValueMap map = mapCommonFields(adCampaign); + if (adCampaign.getBuyingType() != null) { + map.add("buying_type", adCampaign.getBuyingType().name()); + } + return graphApi.publish(getAdAccountId(accountId), "adcampaign_groups", map); } - public void updateAdCampaignObjective(String campaignId, CampaignObjective objective) { + public boolean updateAdCampaign(String campaignId, AdCampaign adCampaign) { requireAuthorization(); - MultiValueMap map = new LinkedMultiValueMap(); - map.add("objective", objective.name()); - graphApi.post(campaignId, map); + MultiValueMap map = mapCommonFields(adCampaign); + return graphApi.update(campaignId, map); } - public void updateAdCampaignSpendCap(String campaignId, int spendCap) { + public void deleteAdCampaign(String campaignId) { requireAuthorization(); MultiValueMap map = new LinkedMultiValueMap(); - map.add("spend_cap", String.valueOf(spendCap)); + map.add("campaign_group_status", CampaignStatus.DELETED.name()); graphApi.post(campaignId, map); } - public void deleteAdCampaign(String campaignId) { - requireAuthorization(); + private MultiValueMap mapCommonFields(AdCampaign adCampaign) { MultiValueMap map = new LinkedMultiValueMap(); - map.add("campaign_group_status", CampaignStatus.DELETED.name()); - graphApi.post(campaignId, map); + if (adCampaign.getName() != null) { + map.add("name", adCampaign.getName()); + } + if (adCampaign.getStatus() != null) { + map.add("campaign_group_status", adCampaign.getStatus().name()); + } + if (adCampaign.getObjective() != null) { + map.add("objective", adCampaign.getObjective().name()); + } + if (adCampaign.getSpendCap() != 0) { + map.add("spend_cap", String.valueOf(adCampaign.getSpendCap())); + } + return map; } -} +} \ No newline at end of file diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java index e1af9ae40..069b560fd 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java @@ -44,7 +44,7 @@ abstract public class AdCampaignMixin extends FacebookObjectMixin { private CampaignObjective objective; @JsonProperty("spend_cap") - private String spendCap; + private int spendCap; private static class BuyingTypeDeserializer extends JsonDeserializer { diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java index b68645419..d07f72d67 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java @@ -225,21 +225,21 @@ public void getAdAccountCampaigns() throws Exception { assertEquals(CampaignStatus.ACTIVE, campaigns.get(0).getStatus()); assertEquals("Campaign #1", campaigns.get(0).getName()); assertEquals(CampaignObjective.POST_ENGAGEMENT, campaigns.get(0).getObjective()); - assertEquals(null, campaigns.get(0).getSpendCap()); + assertEquals(0, campaigns.get(0).getSpendCap()); assertEquals("602123456789", campaigns.get(1).getId()); assertEquals("123456789", campaigns.get(1).getAccountId()); assertEquals(BuyingType.FIXED_CPM, campaigns.get(1).getBuyingType()); assertEquals(CampaignStatus.PAUSED, campaigns.get(1).getStatus()); assertEquals("Campaign #2", campaigns.get(1).getName()); assertEquals(CampaignObjective.NONE, campaigns.get(1).getObjective()); - assertEquals(null, campaigns.get(1).getSpendCap()); + assertEquals(0, campaigns.get(1).getSpendCap()); assertEquals("603123456789", campaigns.get(2).getId()); assertEquals("123456789", campaigns.get(2).getAccountId()); assertEquals(BuyingType.RESERVED, campaigns.get(2).getBuyingType()); assertEquals(CampaignStatus.ARCHIVED, campaigns.get(2).getStatus()); assertEquals("Campaign #3", campaigns.get(2).getName()); assertEquals(CampaignObjective.WEBSITE_CONVERSIONS, campaigns.get(2).getObjective()); - assertEquals("50000", campaigns.get(2).getSpendCap()); + assertEquals(50000, campaigns.get(2).getSpendCap()); } @Test diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index a9ee6c68a..47f7ace71 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -5,6 +5,7 @@ import org.springframework.social.NotAuthorizedException; import org.springframework.social.facebook.api.InvalidCampaignStatusException; +import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AbstractFacebookAdsApiTest; import org.springframework.social.facebook.api.ads.AdCampaign; import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; @@ -12,6 +13,7 @@ import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; @@ -24,6 +26,42 @@ */ public class CampaignTemplateTest extends AbstractFacebookAdsApiTest { + @Test + public void getAdCampaigns() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups?fields=id%2Caccount_id%2Cbuying_type%2Ccampaign_group_status%2Cname%2Cobjective%2Cspend_cap")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-account-campaigns"), MediaType.APPLICATION_JSON)); + PagedList campaigns = facebookAds.campaignOperations().getAdCampaigns("123456789"); + assertEquals(3, campaigns.size()); + assertEquals("601123456789", campaigns.get(0).getId()); + assertEquals("123456789", campaigns.get(0).getAccountId()); + assertEquals(BuyingType.AUCTION, campaigns.get(0).getBuyingType()); + assertEquals(CampaignStatus.ACTIVE, campaigns.get(0).getStatus()); + assertEquals("Campaign #1", campaigns.get(0).getName()); + assertEquals(CampaignObjective.POST_ENGAGEMENT, campaigns.get(0).getObjective()); + assertEquals(0, campaigns.get(0).getSpendCap()); + assertEquals("602123456789", campaigns.get(1).getId()); + assertEquals("123456789", campaigns.get(1).getAccountId()); + assertEquals(BuyingType.FIXED_CPM, campaigns.get(1).getBuyingType()); + assertEquals(CampaignStatus.PAUSED, campaigns.get(1).getStatus()); + assertEquals("Campaign #2", campaigns.get(1).getName()); + assertEquals(CampaignObjective.NONE, campaigns.get(1).getObjective()); + assertEquals(0, campaigns.get(1).getSpendCap()); + assertEquals("603123456789", campaigns.get(2).getId()); + assertEquals("123456789", campaigns.get(2).getAccountId()); + assertEquals(BuyingType.RESERVED, campaigns.get(2).getBuyingType()); + assertEquals(CampaignStatus.ARCHIVED, campaigns.get(2).getStatus()); + assertEquals("Campaign #3", campaigns.get(2).getName()); + assertEquals(CampaignObjective.WEBSITE_CONVERSIONS, campaigns.get(2).getObjective()); + assertEquals(50000, campaigns.get(2).getSpendCap()); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdCampaigns_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().getAdCampaigns("123456789"); + } + @Test public void getCampaign() throws Exception { mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789?fields=id%2Caccount_id%2Cbuying_type%2Ccampaign_group_status%2Cname%2Cobjective%2Cspend_cap")) @@ -38,7 +76,7 @@ public void getCampaign() throws Exception { assertEquals(CampaignStatus.ACTIVE, campaign.getStatus()); assertEquals("The test campaign name", campaign.getName()); assertEquals(CampaignObjective.POST_ENGAGEMENT, campaign.getObjective()); - assertEquals("1000", campaign.getSpendCap()); + assertEquals(1000, campaign.getSpendCap()); } @Test @@ -64,14 +102,30 @@ public void getCampaign_unauthorized() throws Exception { } @Test - public void createCampaign_withNameAndStatus() throws Exception { - String requestBody = "name=Campaign+created+by+SpringSocialFacebook&campaign_group_status=PAUSED"; + public void createCampaign_withNameOnly() throws Exception { + String requestBody = "name=Campaign+created+by+SpringSocialFacebook"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED)); + AdCampaign campaign = new AdCampaign(); + campaign.setName("Campaign created by SpringSocialFacebook"); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", campaign)); + mockServer.verify(); + } + + @Test + public void createCampaign_withStatusOnly() throws Exception { + String requestBody = "campaign_group_status=PAUSED"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setStatus(CampaignStatus.PAUSED); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", campaign)); mockServer.verify(); } @@ -85,8 +139,11 @@ public void createCampaign_withInvalidStatus() throws Exception { .andExpect(content().string(requestBody)) .andRespond(withBadRequest().body(jsonResource("error-invalid-update-campaign-status")).contentType(MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setName("Campaign with invalid status"); + campaign.setStatus(CampaignStatus.ARCHIVED); try { - facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with invalid status", CampaignStatus.ARCHIVED); + facebookAds.campaignOperations().createAdCampaign("123456789", campaign); fail(); } catch (InvalidCampaignStatusException e) { assertEquals("New campaigns need to be either active or paused.", e.getMessage()); @@ -95,115 +152,147 @@ public void createCampaign_withInvalidStatus() throws Exception { } @Test - public void createCampaign_withObjective() throws Exception { - String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES&spend_cap=50000"; + public void createCampaign_withObjectiveOnly() throws Exception { + String requestBody = "objective=VIDEO_VIEWS"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setObjective(CampaignObjective.VIDEO_VIEWS); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", campaign)); + mockServer.verify(); + } + + @Test + public void createCampaign_withSpendCapOnly() throws Exception { + String requestBody = "spend_cap=240000"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with objective", - CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, "50000")); + AdCampaign campaign = new AdCampaign(); + campaign.setSpendCap(240000); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", campaign)); mockServer.verify(); } @Test - public void createCampaign_withoutSpendCap() throws Exception { - String requestBody = "name=Campaign+with+spend+cap&campaign_group_status=ACTIVE&objective=PAGE_LIKES"; + public void createCampaign_withBuyingTypeOnly() throws Exception { + String requestBody = "buying_type=AUCTION"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with spend cap", - CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, null)); + AdCampaign campaign = new AdCampaign(); + campaign.setBuyingType(BuyingType.AUCTION); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", campaign)); mockServer.verify(); } + @Test - public void createCampaign_withBuyingType() throws Exception { - String requestBody = "name=Campaign+with+objective&campaign_group_status=ACTIVE&objective=PAGE_LIKES&spend_cap=50000&buying_type=AUCTION"; + public void createCampaign_withAllFields() throws Exception { + String requestBody = "name=Full+campaign&campaign_group_status=ACTIVE&objective=PAGE_LIKES&spend_cap=60000&buying_type=RESERVED"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaign_groups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"601123456789\"}", MediaType.APPLICATION_JSON)); - assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign with objective", - CampaignStatus.ACTIVE, CampaignObjective.PAGE_LIKES, "50000", BuyingType.AUCTION)); + AdCampaign campaign = new AdCampaign(); + campaign.setName("Full campaign"); + campaign.setStatus(CampaignStatus.ACTIVE); + campaign.setObjective(CampaignObjective.PAGE_LIKES); + campaign.setSpendCap(60000); + campaign.setBuyingType(BuyingType.RESERVED); + assertEquals("601123456789", facebookAds.campaignOperations().createAdCampaign("123456789", campaign)); mockServer.verify(); } @Test(expected = NotAuthorizedException.class) public void createCampaign_unauthorized() throws Exception { - unauthorizedFacebookAds.campaignOperations().createAdCampaign("act_123456789", "Campaign created by SpringSocialFacebook", CampaignStatus.PAUSED); + unauthorizedFacebookAds.campaignOperations().createAdCampaign("123456789", new AdCampaign()); } @Test - public void updateAdCampaignName() throws Exception { + public void updateAdCampaign_withNameOnly() throws Exception { String requestBody = "name=New+campaign+name"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); - facebookAds.campaignOperations().updateAdCampaignName("600123456789", "New campaign name"); + .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setName("New campaign name"); + assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); mockServer.verify(); } - @Test(expected = NotAuthorizedException.class) - public void updateAdCampaignName_unauthorized() throws Exception { - unauthorizedFacebookAds.campaignOperations().updateAdCampaignName("600123456789", "New campaign name"); - } - @Test - public void updateAdCampaignStatus() throws Exception { + public void updateAdCampaign_withStatusOnly() throws Exception { String requestBody = "campaign_group_status=ACTIVE"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); - facebookAds.campaignOperations().updateAdCampaignStatus("600123456789", CampaignStatus.ACTIVE); + .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setStatus(CampaignStatus.ACTIVE); + assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); mockServer.verify(); } - @Test(expected = NotAuthorizedException.class) - public void updateAdCampaignStatus_unauthorized() throws Exception { - unauthorizedFacebookAds.campaignOperations().updateAdCampaignStatus("600123456789", CampaignStatus.ACTIVE); - } - @Test - public void updateAdCampaignObjective() throws Exception { + public void updateAdCampaign_withObjectiveOnly() throws Exception { String requestBody = "objective=POST_ENGAGEMENT"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); - facebookAds.campaignOperations().updateAdCampaignObjective("600123456789", CampaignObjective.POST_ENGAGEMENT); + .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setObjective(CampaignObjective.POST_ENGAGEMENT); + assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); mockServer.verify(); } - @Test(expected = NotAuthorizedException.class) - public void updateAdCampaignObjective_unauthorized() throws Exception { - unauthorizedFacebookAds.campaignOperations().updateAdCampaignObjective("600123456789", CampaignObjective.POST_ENGAGEMENT); + @Test + public void updateAdCampaign_withSpendCapOnly() throws Exception { + String requestBody = "spend_cap=60000"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setSpendCap(60000); + assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); + mockServer.verify(); } @Test - public void updateAdCampaignSpendCap() throws Exception { - String requestBody = "spend_cap=60000"; + public void updateAdCampaign_withAllFields() throws Exception { + String requestBody = "name=Updated+campaign&campaign_group_status=ARCHIVED&objective=CANVAS_APP_ENGAGEMENT&spend_cap=60000"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); - facebookAds.campaignOperations().updateAdCampaignSpendCap("600123456789", 60000); + .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + AdCampaign campaign = new AdCampaign(); + campaign.setName("Updated campaign"); + campaign.setStatus(CampaignStatus.ARCHIVED); + campaign.setObjective(CampaignObjective.CANVAS_APP_ENGAGEMENT); + campaign.setSpendCap(60000); + assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); mockServer.verify(); } @Test(expected = NotAuthorizedException.class) - public void updateAdCampaignSpendCap_unauthorized() throws Exception { - unauthorizedFacebookAds.campaignOperations().updateAdCampaignSpendCap("600123456789", 60000); + public void updateAdCampaign_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().updateAdCampaign("600123456789", new AdCampaign()); } @Test From 9b78b7a37e7a94507d136f8e2ba6c61bb68015ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Thu, 14 May 2015 15:21:53 +0200 Subject: [PATCH 13/28] Moves files to right packages. --- .../social/facebook/api/{ => ads}/AdSet.java | 4 +++- .../social/facebook/api/{ => ads}/AdSetOperations.java | 2 +- .../facebook/api/{ => ads}/impl/json/AdSetMixin.java | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdSet.java (94%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/AdSetOperations.java (81%) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/impl/json/AdSetMixin.java (90%) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSet.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java similarity index 94% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSet.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java index d493d11b9..96c4ecbce 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSet.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java @@ -1,4 +1,6 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.facebook.api.FacebookObject; import java.util.Date; import java.util.List; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSetOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java similarity index 81% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSetOperations.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java index dabe307c9..184a06566 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/AdSetOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java @@ -1,4 +1,4 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; /** * Defines operations for working with Facebook Ad set object. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdSetMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java similarity index 90% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdSetMixin.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java index c00f5d2b2..5745ebe4a 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/AdSetMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java @@ -1,4 +1,4 @@ -package org.springframework.social.facebook.api.impl.json; +package org.springframework.social.facebook.api.ads.impl.json; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -7,9 +7,9 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.springframework.social.facebook.api.AdSet; -import org.springframework.social.facebook.api.AdSet.AdSetStatus; -import org.springframework.social.facebook.api.AdSet.BidType; +import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; +import org.springframework.social.facebook.api.ads.AdSet.BidType; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; import java.io.IOException; import java.util.Date; From 6fa8690ca765c1bdf3020a22841a948ab9dee609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Thu, 14 May 2015 15:26:46 +0200 Subject: [PATCH 14/28] Corrects documentation on AccountOperations interface. --- .../social/facebook/api/ads/AccountOperations.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AccountOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AccountOperations.java index 8e0543e6d..fb80e4379 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AccountOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AccountOperations.java @@ -3,7 +3,7 @@ import org.springframework.social.ApiException; import org.springframework.social.InsufficientPermissionException; import org.springframework.social.MissingAuthorizationException; -import org.springframework.social.facebook.api.*; +import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AdUser.AdUserRole; /** @@ -78,7 +78,7 @@ public interface AccountOperations { * Add the user to the ad account. * * @param accountId the ID of the ad account (account_id) - * @param userId the id of an user + * @param userId the id of an user (App Scoped User ID) * @param role the role for the new user in ad account * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. @@ -90,7 +90,7 @@ public interface AccountOperations { * Remove user's access to an ad account. * * @param accountId the ID of the ad account (account_id) - * @param userId the id of an user + * @param userId the id of an user (App Scoped User ID) * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. From d74bfc1b13bf881f5a981161f98fa6c2a838ff18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Thu, 28 May 2015 11:21:10 +0200 Subject: [PATCH 15/28] Correct mapping of AdSet. Adds implementation of createAdSet. --- .../social/facebook/api/ads/AdSet.java | 55 ++- .../facebook/api/ads/AdSetOperations.java | 20 +- .../social/facebook/api/ads/BidInfo.java | 9 + .../social/facebook/api/ads/FacebookAds.java | 1 + .../social/facebook/api/ads/Targeting.java | 319 ++++++++++++++++++ .../facebook/api/ads/TargetingCityEntry.java | 31 ++ .../facebook/api/ads/TargetingEntry.java | 25 ++ .../facebook/api/ads/TargetingLocation.java | 80 +++++ .../facebook/api/ads/impl/AdSetTemplate.java | 63 ++++ .../api/ads/impl/FacebookAdsTemplate.java | 8 +- .../api/ads/impl/json/AdAccountMixin.java | 12 +- .../api/ads/impl/json/AdSetMixin.java | 15 +- .../api/ads/impl/json/BidInfoMixin.java | 11 + .../impl/json/TargetingCityEntryMixin.java | 21 ++ .../ads/impl/json/TargetingEntryMixin.java | 15 + .../ads/impl/json/TargetingLocationMixin.java | 82 +++++ .../json/TargetingLocationSerializer.java | 51 +++ .../api/ads/impl/json/TargetingMixin.java | 95 ++++++ .../ads/impl/json/TargetingSerializer.java | 79 +++++ .../api/impl/AbstractFacebookOperations.java | 7 +- .../api/impl/json/FacebookModule.java | 7 + .../api/impl/json/FacebookObjectMixin.java | 2 +- .../facebook/api/ads/AdSetTemplateTest.java | 199 +++++++++++ .../social/facebook/api/ads/ad-set.json | 106 ++++++ 24 files changed, 1287 insertions(+), 26 deletions(-) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingCityEntry.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingEntry.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingSerializer.java create mode 100644 spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java index 96c4ecbce..047b4de11 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java @@ -18,7 +18,7 @@ public class AdSet extends FacebookObject { private AdSetStatus status; private boolean autobid; - private Object bidInfo; + private BidInfo bidInfo; private BidType bidType; private int budgetRemaining; @@ -26,8 +26,7 @@ public class AdSet extends FacebookObject { private int lifetimeBudget; private List creativeSequence; - private Object promotedObject; - private Object targeting; + private Targeting targeting; private Date startTime; private Date endTime; @@ -46,26 +45,50 @@ public String getCampaignId() { return campaignId; } + public void setCampaignId(String campaignId) { + this.campaignId = campaignId; + } + public String getName() { return name; } + public void setName(String name) { + this.name = name; + } + public AdSetStatus getStatus() { return status; } + public void setStatus(AdSetStatus status) { + this.status = status; + } + public boolean isAutobid() { return autobid; } - public Object getBidInfo() { + public void setAutobid(boolean autobid) { + this.autobid = autobid; + } + + public BidInfo getBidInfo() { return bidInfo; } + public void setBidInfo(BidInfo bidInfo) { + this.bidInfo = bidInfo; + } + public BidType getBidType() { return bidType; } + public void setBidType(BidType bidType) { + this.bidType = bidType; + } + public int getBudgetRemaining() { return budgetRemaining; } @@ -74,30 +97,46 @@ public int getDailyBudget() { return dailyBudget; } + public void setDailyBudget(int dailyBudget) { + this.dailyBudget = dailyBudget; + } + public int getLifetimeBudget() { return lifetimeBudget; } + public void setLifetimeBudget(int lifetimeBudget) { + this.lifetimeBudget = lifetimeBudget; + } + public List getCreativeSequence() { return creativeSequence; } - public Object getPromotedObject() { - return promotedObject; + public Targeting getTargeting() { + return targeting; } - public Object getTargeting() { - return targeting; + public void setTargeting(Targeting targeting) { + this.targeting = targeting; } public Date getStartTime() { return startTime; } + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + public Date getEndTime() { return endTime; } + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + public Date getCreatedTime() { return createdTime; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java index 184a06566..4b0c82c99 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java @@ -7,7 +7,25 @@ */ public interface AdSetOperations { static final String[] AD_SET_FIELDS = { - "account_id", "bid_info", "bid_type" + "account_id", "bid_info", "bid_type", "budget_remaining", "campaign_group_id", "campaign_status", "created_time", + "creative_sequence", "daily_budget", "end_time", "id", "is_autobid", "lifetime_budget", "name", "promoted_object", + "start_time", "targeting", "updated_time" }; + /** + * Gets ad set by given id. + * + * @param id the id of the ad set + * @return the {@link AdSet} object + */ + AdSet getAdSet(String id); + + /** + * Creates an ad set in the given account + * + * @param accountId the account id + * @param adSet the ad set object + * @return the id of the new ad set. + */ + String createAdSet(String accountId, AdSet adSet); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java new file mode 100644 index 000000000..f8d6f5698 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java @@ -0,0 +1,9 @@ +package org.springframework.social.facebook.api.ads; + +import java.util.HashMap; + +/** + * @author Sebastian Górecki + */ +public class BidInfo extends HashMap { +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java index 757ff90a4..7383901a2 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java @@ -11,4 +11,5 @@ public interface FacebookAds { AccountOperations accountOperations(); CampaignOperations campaignOperations(); + AdSetOperations adSetOperations(); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java new file mode 100644 index 000000000..dc135f5f3 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java @@ -0,0 +1,319 @@ +package org.springframework.social.facebook.api.ads; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.List; + +/** + * @author Sebastian Górecki + */ +public class Targeting { + + // demographics + private List genders; + private Integer ageMin; + private Integer ageMax; + private List relationshipStatuses; + private List interestedIn; + + // location + private TargetingLocation geoLocations; + private TargetingLocation excludedGeoLocations; + + // placement + private List pageTypes; + + // connections + private List connections; + private List excludedConnections; + private List friendsOfConnections; + + // interests + private List interests; + + // behaviors + private List behaviors; + + // education and workplace + private List educationSchools; + private List educationStatuses; + private List collegeYears; + private List educationMajors; + private List workEmployers; + private List workPositions; + + public List getGenders() { + return genders; + } + + public void setGenders(List genders) { + this.genders = genders; + } + + public Integer getAgeMin() { + return ageMin; + } + + public void setAgeMin(Integer ageMin) { + this.ageMin = ageMin; + } + + public Integer getAgeMax() { + return ageMax; + } + + public void setAgeMax(Integer ageMax) { + this.ageMax = ageMax; + } + + public List getRelationshipStatuses() { + return relationshipStatuses; + } + + public void setRelationshipStatuses(List relationshipStatuses) { + this.relationshipStatuses = relationshipStatuses; + } + + public List getInterestedIn() { + return interestedIn; + } + + public void setInterestedIn(List interestedIn) { + this.interestedIn = interestedIn; + } + + public TargetingLocation getGeoLocations() { + return geoLocations; + } + + public void setGeoLocations(TargetingLocation geoLocations) { + this.geoLocations = geoLocations; + } + + public TargetingLocation getExcludedGeoLocations() { + return excludedGeoLocations; + } + + public void setExcludedGeoLocations(TargetingLocation excludedGeoLocations) { + this.excludedGeoLocations = excludedGeoLocations; + } + + public List getPageTypes() { + return pageTypes; + } + + public void setPageTypes(List pageTypes) { + this.pageTypes = pageTypes; + } + + public List getConnections() { + return connections; + } + + public void setConnections(List connections) { + this.connections = connections; + } + + public List getExcludedConnections() { + return excludedConnections; + } + + public void setExcludedConnections(List excludedConnections) { + this.excludedConnections = excludedConnections; + } + + public List getFriendsOfConnections() { + return friendsOfConnections; + } + + public void setFriendsOfConnections(List friendsOfConnections) { + this.friendsOfConnections = friendsOfConnections; + } + + public List getInterests() { + return interests; + } + + public void setInterests(List interests) { + this.interests = interests; + } + + public List getBehaviors() { + return behaviors; + } + + public void setBehaviors(List behaviors) { + this.behaviors = behaviors; + } + + public List getEducationSchools() { + return educationSchools; + } + + public void setEducationSchools(List educationSchools) { + this.educationSchools = educationSchools; + } + + public List getEducationStatuses() { + return educationStatuses; + } + + public void setEducationStatuses(List educationStatuses) { + this.educationStatuses = educationStatuses; + } + + public List getCollegeYears() { + return collegeYears; + } + + public void setCollegeYears(List collegeYears) { + this.collegeYears = collegeYears; + } + + public List getEducationMajors() { + return educationMajors; + } + + public void setEducationMajors(List educationMajors) { + this.educationMajors = educationMajors; + } + + public List getWorkEmployers() { + return workEmployers; + } + + public void setWorkEmployers(List workEmployers) { + this.workEmployers = workEmployers; + } + + public List getWorkPositions() { + return workPositions; + } + + public void setWorkPositions(List workPositions) { + this.workPositions = workPositions; + } + + public enum Gender { + UNKNOWN(0), MALE(1), FEMALE(2); + + private final int value; + + Gender(int value) { + this.value = value; + } + + @JsonCreator + public static Gender forValue(int value) { + for (Gender gender : Gender.values()) { + if (gender.getValue() == value) return gender; + } + return UNKNOWN; + } + + @JsonValue + public int getValue() { + return value; + } + } + + public enum RelationshipStatus { + UNKNOWN(0), SINGLE(1), IN_RELATIONSHIP(2), MARRIED(3), ENGAGED(4), NOT_SPECIFIED(6), IN_CIVIL_UNION(7), + IN_DOMESTIC_PARTNERSHIP(8), IN_OPEN_RELATIONSHIP(9), ITS_COMPLICATED(10), SEPARATED(11), DIVORCED(12), + WIDOWED(13); + + private final int value; + + RelationshipStatus(int value) { + this.value = value; + } + + @JsonCreator + public static RelationshipStatus forValue(int value) { + for (RelationshipStatus status : RelationshipStatus.values()) { + if (status.getValue() == value) return status; + } + return UNKNOWN; + } + + @JsonValue + public int getValue() { + return value; + } + } + + public enum InterestedIn { + UNKNOWN(0), MEN(1), WOMEN(2), MAN_AND_WOMAN(3), NOT_SPECIFIED(4); + + private final int value; + + InterestedIn(int value) { + this.value = value; + } + + @JsonCreator + public static InterestedIn forValue(int value) { + for (InterestedIn interestedIn : InterestedIn.values()) { + if (interestedIn.getValue() == value) return interestedIn; + } + return UNKNOWN; + } + + @JsonValue + public int getValue() { + return value; + } + } + + public enum PageType { + UNKNOWN("unknown"), DESKTOP("desktop"), FEED("feed"), DESKTOP_FEED("desktopfeed"), MOBILE("mobile"), + MOBILE_FEED_AND_EXTERNAL("mobilefeed-and-external"), MOBILE_FEED("mobilefeed"), RIGHTCOLUMN("rightcolumn"), + RIGHTCOLUMN_AND_MOBILE("rightcolumn-and-mobile"), HOME("home"), DESKTOP_AND_MOBILE_AND_EXTERNAL("desktop-and-mobile-and-external"), + FEED_AND_EXTERNAL("feed-and-external"), RIGHTCOLUMN_AND_MOBILE_AND_EXTERNAL("rightcolumn-and-mobile-and-external"); + + private final String value; + + PageType(String value) { + this.value = value; + } + + @JsonCreator + public static PageType forValue(String value) { + for (PageType type : PageType.values()) { + if (type.getValue().equals(value)) return type; + } + return UNKNOWN; + } + + @JsonValue + public String getValue() { + return value; + } + } + + public enum EducationStatus { + UNKNOWN(0), HIGH_SCHOOL(1), UNDERGRAD(2), ALUM(3), HIGH_SCHOOL_GRAD(4), SOME_COLLEGE(5), ASSOCIATE_DEGREE(6), + IN_GRAD_SCHOOL(7), SOME_GRAD_SCHOOL(8), MASTER_DEGREE(9), PROFESSIONAL_DEGREE(10), DOCTORATE_DEGREE(11), + UNSPECIFIED(12), SOME_HIGH_SCHOOL(13); + + private final int value; + + EducationStatus(int value) { + this.value = value; + } + + @JsonCreator + public static EducationStatus forValue(int value) { + for (EducationStatus status : EducationStatus.values()) { + if (status.getValue() == value) return status; + } + return UNKNOWN; + } + + @JsonValue + public int getValue() { + return value; + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingCityEntry.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingCityEntry.java new file mode 100644 index 000000000..1ab1de3b2 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingCityEntry.java @@ -0,0 +1,31 @@ +package org.springframework.social.facebook.api.ads; + +/** + * @author Sebastian Górecki + */ +public class TargetingCityEntry { + private String key; + private int radius; + private String distanceUnit; + + TargetingCityEntry() { + } + + public TargetingCityEntry(String key, int radius, String distanceUnit) { + this.key = key; + this.radius = radius; + this.distanceUnit = distanceUnit; + } + + public String getKey() { + return key; + } + + public int getRadius() { + return radius; + } + + public String getDistanceUnit() { + return distanceUnit; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingEntry.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingEntry.java new file mode 100644 index 000000000..51314fee7 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingEntry.java @@ -0,0 +1,25 @@ +package org.springframework.social.facebook.api.ads; + +/** + * @author Sebastian Górecki + */ +public class TargetingEntry { + private long id; + private String name; + + public TargetingEntry() { + } + + public TargetingEntry(long id, String name) { + this.id = id; + this.name = name; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java new file mode 100644 index 000000000..d3f4eeaef --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java @@ -0,0 +1,80 @@ +package org.springframework.social.facebook.api.ads; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.List; + +/** + * @author Sebastian Górecki + */ +public class TargetingLocation { + private List countries; + private List regions; + private List cities; + private List zips; + private List locationTypes; + + public List getLocationTypes() { + return locationTypes; + } + + public void setLocationTypes(List locationTypes) { + this.locationTypes = locationTypes; + } + + public List getCountries() { + return countries; + } + + public void setCountries(List countries) { + this.countries = countries; + } + + public List getRegions() { + return regions; + } + + public void setRegions(List regions) { + this.regions = regions; + } + + public List getCities() { + return cities; + } + + public void setCities(List cities) { + this.cities = cities; + } + + public List getZips() { + return zips; + } + + public void setZips(List zips) { + this.zips = zips; + } + + public enum LocationType { + UNKNOWN("unknown"), RECENT("recent"), HOME("home"), TRAVEL_IN("travel_in"); + + private final String value; + + LocationType(String value) { + this.value = value; + } + + @JsonCreator + public static LocationType forValue(String value) { + for (LocationType type : LocationType.values()) { + if (type.getValue().equals(value)) return type; + } + return UNKNOWN; + } + + @JsonValue + public String getValue() { + return value; + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java new file mode 100644 index 000000000..2fc5842b9 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java @@ -0,0 +1,63 @@ +package org.springframework.social.facebook.api.ads.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.social.facebook.api.GraphApi; +import org.springframework.social.facebook.api.ads.AdSet; +import org.springframework.social.facebook.api.ads.AdSetOperations; +import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +/** + * @author Sebastian Górecki + */ +public class AdSetTemplate extends AbstractFacebookOperations implements AdSetOperations { + private GraphApi graphApi; + private RestTemplate restTemplate; + private ObjectMapper mapper; + + public AdSetTemplate(GraphApi graphApi, RestTemplate restTemplate, ObjectMapper mapper, boolean authorized) { + super(authorized); + this.graphApi = graphApi; + this.restTemplate = restTemplate; + this.mapper = mapper; + } + + public AdSet getAdSet(String id) { + return graphApi.fetchObject(id, AdSet.class, AdSetOperations.AD_SET_FIELDS); + } + + public String createAdSet(String accountId, AdSet adSet) { + MultiValueMap data = new LinkedMultiValueMap(); + data.set("date_format", "U"); + data.set("campaign_group_id", adSet.getCampaignId()); + data.set("name", adSet.getName()); + data.set("campaign_status", adSet.getStatus().name()); + data.set("is_autobid", String.valueOf(adSet.isAutobid())); + if (adSet.getBidInfo() != null) { + try { + data.set("bid_info", mapper.writeValueAsString(adSet.getBidInfo())); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + data.set("bid_type", adSet.getBidType().name()); + data.set("daily_budget", String.valueOf(adSet.getDailyBudget())); + data.set("lifetime_budget", String.valueOf(adSet.getLifetimeBudget())); + try { + data.set("targeting", mapper.writeValueAsString(adSet.getTargeting())); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + if (adSet.getStartTime() != null) { + data.set("start_time", getUnixTime(adSet.getStartTime())); + } + if (adSet.getEndTime() != null) { + data.set("end_time", getUnixTime(adSet.getEndTime())); + } + + return graphApi.publish(getAdAccountId(accountId), "adcampaigns", data); + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java index 11434a41d..e8a50144e 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java @@ -1,8 +1,6 @@ package org.springframework.social.facebook.api.ads.impl; -import org.springframework.social.facebook.api.ads.AccountOperations; -import org.springframework.social.facebook.api.ads.CampaignOperations; -import org.springframework.social.facebook.api.ads.FacebookAds; +import org.springframework.social.facebook.api.ads.*; import org.springframework.social.facebook.api.ads.impl.AccountTemplate; import org.springframework.social.facebook.api.ads.impl.CampaignTemplate; import org.springframework.social.facebook.api.impl.FacebookTemplate; @@ -16,6 +14,7 @@ public class FacebookAdsTemplate extends FacebookTemplate implements FacebookAds private AccountOperations accountOperations; private CampaignOperations campaignOperations; + private AdSetOperations adSetOperations; public FacebookAdsTemplate() { super(null); @@ -35,8 +34,11 @@ public CampaignOperations campaignOperations() { return campaignOperations; } + public AdSetOperations adSetOperations() { return adSetOperations; } + private void initSubApis() { accountOperations = new AccountTemplate(this, getRestTemplate(), isAuthorized()); campaignOperations = new CampaignTemplate(this, getRestTemplate(), isAuthorized()); + adSetOperations = new AdSetTemplate(this, getRestTemplate(), getJsonMessageConverter().getObjectMapper(), isAuthorized()); } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java index 4e57223c3..ada9d6ff0 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java @@ -201,10 +201,14 @@ public List deserialize(JsonParser jp, DeserializationContext ctxt) thro mapper.registerModule(new FacebookModule()); jp.setCodec(mapper); if (jp.hasCurrentToken()) { - JsonNode dataNode = jp.readValueAs(JsonNode.class).get("data"); - if (dataNode != null) { - return (List) mapper.reader(new TypeReference>() { - }).readValue(dataNode); + try { + JsonNode dataNode = jp.readValueAs(JsonNode.class).get("data"); + if (dataNode != null) { + return (List) mapper.reader(new TypeReference>() { + }).readValue(dataNode); + } + } catch (IOException e) { + return Collections.emptyList(); } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java index 5745ebe4a..135bc29de 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; import org.springframework.social.facebook.api.ads.AdSet.BidType; +import org.springframework.social.facebook.api.ads.BidInfo; +import org.springframework.social.facebook.api.ads.Targeting; import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; import java.io.IOException; @@ -34,7 +36,7 @@ abstract public class AdSetMixin extends FacebookObjectMixin { @JsonProperty("name") private String name; - @JsonProperty("campaign_stats") + @JsonProperty("campaign_status") @JsonDeserialize(using = AdSetStatusDeserializer.class) private AdSetStatus status; @@ -42,7 +44,7 @@ abstract public class AdSetMixin extends FacebookObjectMixin { private boolean autobid; @JsonProperty("bid_info") - private Object bidInfo; + private BidInfo bidInfo; @JsonProperty("bid_type") @JsonDeserialize(using = BidTypeDeserializer.class) @@ -60,11 +62,8 @@ abstract public class AdSetMixin extends FacebookObjectMixin { @JsonProperty("creative_sequence") private List creativeSequence; - @JsonProperty("promoted_object") - private Object promotedObject; - @JsonProperty("targeting") - private Object targeting; + private Targeting targeting; @JsonProperty("start_time") private Date startTime; @@ -78,7 +77,7 @@ abstract public class AdSetMixin extends FacebookObjectMixin { @JsonProperty("updated_time") private Date updatedTime; - private class AdSetStatusDeserializer extends JsonDeserializer { + private static class AdSetStatusDeserializer extends JsonDeserializer { @Override public AdSetStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { try { @@ -89,7 +88,7 @@ public AdSetStatus deserialize(JsonParser jp, DeserializationContext ctxt) throw } } - private class BidTypeDeserializer extends JsonDeserializer { + private static class BidTypeDeserializer extends JsonDeserializer { @Override public BidType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { try { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java new file mode 100644 index 000000000..65623c77d --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java @@ -0,0 +1,11 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; + +/** + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class BidInfoMixin extends FacebookObjectMixin { +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java new file mode 100644 index 000000000..1cff1cd62 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java @@ -0,0 +1,21 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; + +/** + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class TargetingCityEntryMixin extends FacebookObjectMixin { + @JsonProperty("key") + private String key; + + @JsonProperty("radius") + private int radius; + + @JsonProperty("distance_unit") + private String distanceUnit; + +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java new file mode 100644 index 000000000..0fa1d43ec --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java @@ -0,0 +1,15 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; + +/** + * @author Sebastian Górecki + */ +public abstract class TargetingEntryMixin extends FacebookObjectMixin { + @JsonProperty("id") + private long id; + + @JsonProperty("name") + private String name; +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java new file mode 100644 index 000000000..8f6648bbb --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java @@ -0,0 +1,82 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.social.facebook.api.ads.TargetingCityEntry; +import org.springframework.social.facebook.api.ads.TargetingLocation.LocationType; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +/** + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonSerialize(using = TargetingLocationSerializer.class) +public abstract class TargetingLocationMixin extends FacebookObjectMixin { + @JsonProperty("countries") + private List countries; + + @JsonProperty("regions") + @JsonDeserialize(using = RegionListDeserializer.class) + private List regions; + + @JsonProperty("cities") + private List cities; + + @JsonProperty("zips") + @JsonDeserialize(using = ZipListDeserializer.class) + private List zips; + + @JsonProperty("location_types") + private List locationTypes; + + private static class RegionListDeserializer extends JsonDeserializer> { + @Override + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + if (jp.getCurrentToken() == JsonToken.START_ARRAY) { + List regions = new ArrayList(); + try { + while (jp.nextToken() != JsonToken.END_ARRAY) { + HashMap regionMap = jp.readValueAs(HashMap.class); + regions.add(regionMap.get("key")); + } + return regions; + } catch (IOException e) { + return Collections.emptyList(); + } + } + return Collections.emptyList(); + } + } + + private static class ZipListDeserializer extends JsonDeserializer> { + @Override + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + if (jp.getCurrentToken() == JsonToken.START_ARRAY) { + List zips = new ArrayList(); + try { + while (jp.nextToken() != JsonToken.END_ARRAY) { + HashMap zipMap = jp.readValueAs(HashMap.class); + zips.add(zipMap.get("key")); + } + return zips; + } catch (IOException e) { + return Collections.emptyList(); + } + } + return Collections.emptyList(); + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java new file mode 100644 index 000000000..90496192d --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java @@ -0,0 +1,51 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.social.facebook.api.ads.TargetingLocation; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Sebastian Górecki + */ +public class TargetingLocationSerializer extends JsonSerializer { + @Override + public void serialize(TargetingLocation location, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeStartObject(); + if (location.getCountries() != null) { + jgen.writeObjectField("countries", location.getCountries()); + } + if (location.getRegions() != null) { + serializeLocationValueEntries("regions", location.getRegions(), jgen); + } + if (location.getCities() != null) { + jgen.writeObjectField("cities", location.getCities()); + } + if (location.getZips() != null) { + serializeLocationValueEntries("zips", location.getZips(), jgen); + } + if (location.getLocationTypes() != null) { + jgen.writeObjectField("location_types", location.getLocationTypes()); + } + jgen.writeEndObject(); + } + + private void serializeLocationValueEntries(String entryName, List entries, JsonGenerator jgen) throws IOException { + jgen.writeFieldName(entryName); + jgen.writeStartArray(); + for (String entry : entries) { + Map map = new HashMap(); + map.put("key", entry); + jgen.writeObject(map); + } + jgen.writeEndArray(); + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java new file mode 100644 index 000000000..f6008f413 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java @@ -0,0 +1,95 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.social.facebook.api.ads.Targeting; +import org.springframework.social.facebook.api.ads.Targeting.*; +import org.springframework.social.facebook.api.ads.TargetingEntry; +import org.springframework.social.facebook.api.ads.TargetingLocation; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonSerialize(using = TargetingSerializer.class) +public abstract class TargetingMixin extends FacebookObjectMixin { + // demographics + @JsonProperty("genders") + private List genders; + + @JsonProperty("age_min") + private Integer ageMin; + + @JsonProperty("age_max") + private Integer ageMax; + + @JsonProperty("relationship_statuses") + private List relationshipStatuses; + + @JsonProperty("interested_in") + private List interestedIn; + + // location + @JsonProperty("geo_locations") + private TargetingLocation geoLocations; + + @JsonProperty("excluded_geo_locations") + private TargetingLocation excludedGeoLocations; + + // placement + @JsonProperty("page_types") + private List pageTypes; + + // connections + @JsonProperty("connections") + private List connections; + + @JsonProperty("excluded_connections") + private List excludedConnections; + + @JsonProperty("friends_of_connections") + private List friendsOfConnections; + + // interests + @JsonProperty("interests") + private List interests; + + // behaviors + @JsonProperty("behaviors") + private List behaviors; + + // education and workplace + @JsonProperty("education_schools") + private List educationSchools; + + @JsonProperty("education_statuses") + private List educationStatuses; + + @JsonProperty("college_years") + private List collegeYears; + + @JsonProperty("education_majors") + private List educationMajors; + + @JsonProperty("work_employers") + private List workEmployers; + + @JsonProperty("work_positions") + private List workPositions; +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingSerializer.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingSerializer.java new file mode 100644 index 000000000..88ba845ea --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingSerializer.java @@ -0,0 +1,79 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.social.facebook.api.ads.Targeting; + +import java.io.IOException; + +/** + * @author Sebastian Górecki + */ +public class TargetingSerializer extends JsonSerializer { + @Override + public void serialize(Targeting targeting, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeStartObject(); + + if (targeting.getGenders() != null) { + jgen.writeObjectField("genders", targeting.getGenders()); + } + if (targeting.getAgeMin() != null) { + jgen.writeObjectField("age_min", targeting.getAgeMin()); + } + if (targeting.getAgeMax() != null) { + jgen.writeObjectField("age_max", targeting.getAgeMax()); + } + if (targeting.getRelationshipStatuses() != null) { + jgen.writeObjectField("relationship_statuses", targeting.getRelationshipStatuses()); + } + if (targeting.getInterestedIn() != null) { + jgen.writeObjectField("interested_in", targeting.getInterestedIn()); + } + if (targeting.getGeoLocations() != null) { + jgen.writeObjectField("geo_locations", targeting.getGeoLocations()); + } + if (targeting.getExcludedGeoLocations() != null) { + jgen.writeObjectField("excluded_geo_locations", targeting.getExcludedGeoLocations()); + } + if (targeting.getPageTypes() != null) { + jgen.writeObjectField("page_types", targeting.getPageTypes()); + } + if (targeting.getConnections() != null) { + jgen.writeObjectField("connections", targeting.getConnections()); + } + if (targeting.getExcludedConnections() != null) { + jgen.writeObjectField("excluded_connections", targeting.getExcludedConnections()); + } + if (targeting.getFriendsOfConnections() != null) { + jgen.writeObjectField("friends_of_connections", targeting.getFriendsOfConnections()); + } + if (targeting.getInterests() != null) { + jgen.writeObjectField("interests", targeting.getInterests()); + } + if (targeting.getBehaviors() != null) { + jgen.writeObjectField("behaviors", targeting.getBehaviors()); + } + if (targeting.getEducationSchools() != null) { + jgen.writeObjectField("education_schools", targeting.getEducationSchools()); + } + if (targeting.getEducationStatuses() != null) { + jgen.writeObjectField("education_statuses", targeting.getEducationStatuses()); + } + if (targeting.getCollegeYears() != null) { + jgen.writeObjectField("college_years", targeting.getCollegeYears()); + } + if (targeting.getEducationMajors() != null) { + jgen.writeObjectField("education_majors", targeting.getEducationMajors()); + } + if (targeting.getWorkEmployers() != null) { + jgen.writeObjectField("work_employers", targeting.getWorkEmployers()); + } + if (targeting.getWorkPositions() != null) { + jgen.writeObjectField("work_positions", targeting.getWorkPositions()); + } + + jgen.writeEndObject(); + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java index 1c018f2c0..c28619880 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/AbstractFacebookOperations.java @@ -17,6 +17,8 @@ import org.springframework.social.MissingAuthorizationException; +import java.util.Date; + public class AbstractFacebookOperations { private final boolean isAuthorized; @@ -34,5 +36,8 @@ protected void requireAuthorization() { throw new MissingAuthorizationException("facebook"); } } - + + public String getUnixTime(Date date) { + return date != null ? String.valueOf(date.getTime() / 1000L) : ""; + } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java index fc62dd5ce..4d5bb93ab 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java @@ -132,5 +132,12 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(AdAccount.class, AdAccountMixin.class); context.setMixInAnnotations(AdCampaign.class, AdCampaignMixin.class); + + context.setMixInAnnotations(BidInfo.class, BidInfoMixin.class); + context.setMixInAnnotations(Targeting.class, TargetingMixin.class); + context.setMixInAnnotations(TargetingCityEntry.class, TargetingCityEntryMixin.class); + context.setMixInAnnotations(TargetingEntry.class, TargetingEntryMixin.class); + context.setMixInAnnotations(TargetingLocation.class, TargetingLocationMixin.class); + context.setMixInAnnotations(AdSet.class, AdSetMixin.class); } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java index 2e1b8e431..a8274c36d 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookObjectMixin.java @@ -26,6 +26,6 @@ public abstract class FacebookObjectMixin { @JsonAnySetter - abstract void add(String key, Object value); + protected abstract void add(String key, Object value); } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java new file mode 100644 index 000000000..283a36d5d --- /dev/null +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -0,0 +1,199 @@ +package org.springframework.social.facebook.api.ads; + +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; +import org.springframework.social.facebook.api.ads.AdSet.BidType; + +import java.lang.reflect.Array; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * @author Sebastian Górecki + */ +public class AdSetTemplateTest extends AbstractFacebookAdsApiTest { + + @Test + public void getAdSet() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/700123456789?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-set"), MediaType.APPLICATION_JSON)); + AdSet adSet = facebookAds.adSetOperations().getAdSet("700123456789"); + assertEquals("700123456789", adSet.getId()); + assertEquals("123456789", adSet.getAccountId()); + assertEquals("600123456789", adSet.getCampaignId()); + assertEquals("Test AdSet", adSet.getName()); + assertEquals(AdSetStatus.ACTIVE, adSet.getStatus()); + assertTrue(adSet.isAutobid()); + assertEquals(BidType.ABSOLUTE_OCPM, adSet.getBidType()); + assertEquals(50, adSet.getBudgetRemaining()); + assertEquals(0, adSet.getDailyBudget()); + assertEquals(200, adSet.getLifetimeBudget()); + // targeting + assertEquals(Integer.valueOf(20), adSet.getTargeting().getAgeMax()); + assertEquals(Integer.valueOf(18), adSet.getTargeting().getAgeMin()); + assertEquals(1, adSet.getTargeting().getBehaviors().size()); + assertEquals(6004854404172L, adSet.getTargeting().getBehaviors().get(0).getId()); + assertEquals("Technology late adopters", adSet.getTargeting().getBehaviors().get(0).getName()); + assertEquals(1, adSet.getTargeting().getGenders().size()); + assertEquals(Targeting.Gender.MALE, adSet.getTargeting().getGenders().get(0)); + assertEquals(1, adSet.getTargeting().getGeoLocations().getCountries().size()); + assertEquals("PL", adSet.getTargeting().getGeoLocations().getCountries().get(0)); + assertEquals(2, adSet.getTargeting().getGeoLocations().getRegions().size()); + assertEquals("3847", adSet.getTargeting().getGeoLocations().getRegions().get(0)); + assertEquals("1122", adSet.getTargeting().getGeoLocations().getRegions().get(1)); + assertEquals(2, adSet.getTargeting().getGeoLocations().getCities().size()); + assertEquals("2430536", adSet.getTargeting().getGeoLocations().getCities().get(0).getKey()); + assertEquals(12, adSet.getTargeting().getGeoLocations().getCities().get(0).getRadius()); + assertEquals("mile", adSet.getTargeting().getGeoLocations().getCities().get(0).getDistanceUnit()); + assertEquals("11223344", adSet.getTargeting().getGeoLocations().getCities().get(1).getKey()); + assertEquals(55, adSet.getTargeting().getGeoLocations().getCities().get(1).getRadius()); + assertEquals("kilometer", adSet.getTargeting().getGeoLocations().getCities().get(1).getDistanceUnit()); + assertEquals(2, adSet.getTargeting().getGeoLocations().getZips().size()); + assertEquals("US:94304", adSet.getTargeting().getGeoLocations().getZips().get(0)); + assertEquals("US:00501", adSet.getTargeting().getGeoLocations().getZips().get(1)); + assertEquals(2, adSet.getTargeting().getGeoLocations().getLocationTypes().size()); + assertEquals(TargetingLocation.LocationType.HOME, adSet.getTargeting().getGeoLocations().getLocationTypes().get(0)); + assertEquals(TargetingLocation.LocationType.RECENT, adSet.getTargeting().getGeoLocations().getLocationTypes().get(1)); + assertEquals(1, adSet.getTargeting().getInterests().size()); + assertEquals(6003629266583L, adSet.getTargeting().getInterests().get(0).getId()); + assertEquals("Hard drives", adSet.getTargeting().getInterests().get(0).getName()); + assertEquals(2, adSet.getTargeting().getPageTypes().size()); + assertEquals(Targeting.PageType.FEED, adSet.getTargeting().getPageTypes().get(0)); + assertEquals(Targeting.PageType.DESKTOP_AND_MOBILE_AND_EXTERNAL, adSet.getTargeting().getPageTypes().get(1)); + assertEquals(2, adSet.getTargeting().getRelationshipStatuses().size()); + assertEquals(Targeting.RelationshipStatus.IN_RELATIONSHIP, adSet.getTargeting().getRelationshipStatuses().get(0)); + assertEquals(Targeting.RelationshipStatus.IN_OPEN_RELATIONSHIP, adSet.getTargeting().getRelationshipStatuses().get(1)); + assertEquals(1, adSet.getTargeting().getInterestedIn().size()); + assertEquals(Targeting.InterestedIn.WOMEN, adSet.getTargeting().getInterestedIn().get(0)); + assertEquals(1, adSet.getTargeting().getEducationSchools().size()); + assertEquals(105930651606L, adSet.getTargeting().getEducationSchools().get(0).getId()); + assertEquals("Harvard University", adSet.getTargeting().getEducationSchools().get(0).getName()); + assertEquals(3, adSet.getTargeting().getEducationStatuses().size()); + assertEquals(Targeting.EducationStatus.HIGH_SCHOOL, adSet.getTargeting().getEducationStatuses().get(0)); + assertEquals(Targeting.EducationStatus.MASTER_DEGREE, adSet.getTargeting().getEducationStatuses().get(1)); + assertEquals(Targeting.EducationStatus.SOME_HIGH_SCHOOL, adSet.getTargeting().getEducationStatuses().get(2)); + assertEquals(1, adSet.getTargeting().getWorkEmployers().size()); + assertEquals(50431654L, adSet.getTargeting().getWorkEmployers().get(0).getId()); + assertEquals("Microsoft", adSet.getTargeting().getWorkEmployers().get(0).getName()); + assertEquals(1, adSet.getTargeting().getWorkPositions().size()); + assertEquals(105763692790962L, adSet.getTargeting().getWorkPositions().get(0).getId()); + assertEquals("Business Analyst", adSet.getTargeting().getWorkPositions().get(0).getName()); + + assertEquals(toDate("2015-04-12T09:19:00+0200"), adSet.getStartTime()); + assertEquals(toDate("2015-04-13T09:19:00+0200"), adSet.getEndTime()); + assertEquals(toDate("2015-04-10T09:28:54+0200"), adSet.getCreatedTime()); + assertEquals(toDate("2015-04-10T13:32:09+0200"), adSet.getUpdatedTime()); + } + + @Test + public void createAdSet() throws Exception { + String requestBody = "date_format=U&campaign_group_id=600123456789&name=Test+AdSet&campaign_status=PAUSED&is_autobid=true&bid_type=ABSOLUTE_OCPM&daily_budget=0&lifetime_budget=200&targeting=%7B%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%5D%7D%7D&end_time=1432231200"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"701123456789\"}", MediaType.APPLICATION_JSON)); + AdSet adSet = new AdSet(); + adSet.setAutobid(true); + adSet.setBidType(BidType.ABSOLUTE_OCPM); + adSet.setCampaignId("600123456789"); + adSet.setStatus(AdSetStatus.PAUSED); + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + adSet.setEndTime(formatter.parse("2015-05-21 20:00:00")); + adSet.setName("Test AdSet"); + TargetingLocation location = new TargetingLocation(); + location.setCountries(Arrays.asList("PL")); + Targeting targeting = new Targeting(); + targeting.setGeoLocations(location); + adSet.setTargeting(targeting); + adSet.setLifetimeBudget(200); + + assertEquals("701123456789", facebookAds.adSetOperations().createAdSet("123456789", adSet)); + mockServer.verify(); + } + + @Test + public void createAdSet_withAllFields() throws Exception { + String requestBody = "date_format=U&campaign_group_id=601123456789&name=Test+AdSet+2&campaign_status=ACTIVE&is_autobid=false&" + + "bid_info=%7B%22REACH%22%3A%221000%22%2C%22ACTIONS%22%3A%22200%22%2C%22SOCIAL%22%3A%22110%22%2C%22CLICKS%22%3A%22500%22%7D&" + + "bid_type=ABSOLUTE_OCPM&daily_budget=4000&lifetime_budget=0&" + + "targeting=%7B%22genders%22%3A%5B1%2C2%5D%2C%22age_min%22%3A45%2C%22age_max%22%3A55%2C%22relationship_statuses%22%3A%5B10%2C12%5D%2C%22interested_in%22%3A%5B1%2C2%5D%2C%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%2C%22DE%22%2C%22US%22%2C%22FR%22%5D%2C%22regions%22%3A%5B%7B%22key%22%3A%223847%22%7D%2C%7B%22key%22%3A%221111%22%7D%2C%7B%22key%22%3A%221234%22%7D%2C%7B%22key%22%3A%229888%22%7D%5D%2C%22cities%22%3A%5B%7B%22key%22%3A%222430536%22%2C%22radius%22%3A12%2C%22distance_unit%22%3A%22mile%22%7D%2C%7B%22key%22%3A%22777555%22%2C%22radius%22%3A1024%2C%22distance_unit%22%3A%22kilometer%22%7D%5D%2C%22zips%22%3A%5B%7B%22key%22%3A%22PL%3A62030%22%7D%2C%7B%22key%22%3A%22US%3A88123%22%7D%2C%7B%22key%22%3A%22FR%3A33144%22%7D%5D%2C%22location_types%22%3A%5B%22home%22%2C%22recent%22%5D%7D%2C%22excluded_geo_locations%22%3A%7B%22countries%22%3A%5B%22HU%22%2C%22JP%22%5D%2C%22regions%22%3A%5B%7B%22key%22%3A%221122%22%7D%2C%7B%22key%22%3A%2231415%22%7D%5D%2C%22cities%22%3A%5B%7B%22key%22%3A%2288997766%22%2C%22radius%22%3A12345%2C%22distance_unit%22%3A%22mile%22%7D%5D%2C%22zips%22%3A%5B%7B%22key%22%3A%22JP%3A44552%22%7D%5D%2C%22location_types%22%3A%5B%22home%22%5D%7D%2C%22page_types%22%3A%5B%22desktopfeed%22%2C%22mobilefeed-and-external%22%5D%2C%22connections%22%3A%5B%22123456789%22%2C%2255442211%22%5D%2C%22excluded_connections%22%3A%5B%2233441122%22%5D%2C%22friends_of_connections%22%3A%5B%22987654321%22%5D%2C%22interests%22%3A%5B%7B%22id%22%3A986123123123%2C%22name%22%3A%22Football%22%7D%5D%2C%22behaviors%22%3A%5B%7B%22id%22%3A1%2C%22name%22%3A%22Some+behavior%22%7D%5D%2C%22education_schools%22%3A%5B%7B%22id%22%3A10593123549%2C%22name%22%3A%22Poznan+University+of+Technology%22%7D%5D%2C%22education_statuses%22%3A%5B9%5D%2C%22college_years%22%3A%5B8%5D%2C%22education_majors%22%3A%5B%7B%22id%22%3A12%2C%22name%22%3A%22Some+major%22%7D%5D%2C%22work_employers%22%3A%5B%7B%22id%22%3A43125%2C%22name%22%3A%22Super+company%22%7D%5D%2C%22work_positions%22%3A%5B%7B%22id%22%3A11111%2C%22name%22%3A%22Developer%22%7D%5D%7D&" + + "start_time=1432742400&end_time=1435420799"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"702123456789\"}", MediaType.APPLICATION_JSON)); + AdSet adSet = new AdSet(); + adSet.setCampaignId("601123456789"); + adSet.setName("Test AdSet 2"); + adSet.setStatus(AdSetStatus.ACTIVE); + adSet.setAutobid(false); + BidInfo bidInfo = new BidInfo(); + bidInfo.put("ACTIONS", "200"); + bidInfo.put("REACH", "1000"); + bidInfo.put("CLICKS", "500"); + bidInfo.put("SOCIAL", "110"); + adSet.setBidInfo(bidInfo); + adSet.setBidType(BidType.ABSOLUTE_OCPM); + adSet.setDailyBudget(4000); + adSet.setLifetimeBudget(0); + // targeting + Targeting targeting = new Targeting(); + targeting.setGenders(Arrays.asList(Targeting.Gender.MALE, Targeting.Gender.FEMALE)); + targeting.setAgeMin(45); + targeting.setAgeMax(55); + targeting.setRelationshipStatuses(Arrays.asList(Targeting.RelationshipStatus.ITS_COMPLICATED, Targeting.RelationshipStatus.DIVORCED)); + targeting.setInterestedIn(Arrays.asList(Targeting.InterestedIn.MEN, Targeting.InterestedIn.WOMEN)); + // targeting - geoLocations + TargetingLocation geoLocation = new TargetingLocation(); + geoLocation.setCountries(Arrays.asList("PL", "DE", "US", "FR")); + geoLocation.setRegions(Arrays.asList("3847", "1111", "1234", "9888")); + geoLocation.setCities(Arrays.asList(new TargetingCityEntry("2430536", 12, "mile"), new TargetingCityEntry("777555", 1024, "kilometer"))); + geoLocation.setZips(Arrays.asList("PL:62030", "US:88123", "FR:33144")); + geoLocation.setLocationTypes(Arrays.asList(TargetingLocation.LocationType.HOME, TargetingLocation.LocationType.RECENT)); + targeting.setGeoLocations(geoLocation); + // targeting - excludedGeoLocations + TargetingLocation excludedGeoLocations = new TargetingLocation(); + excludedGeoLocations.setCountries(Arrays.asList("HU", "JP")); + excludedGeoLocations.setRegions(Arrays.asList("1122", "31415")); + excludedGeoLocations.setCities(Arrays.asList(new TargetingCityEntry("88997766", 12345, "mile"))); + excludedGeoLocations.setZips(Arrays.asList("JP:44552")); + excludedGeoLocations.setLocationTypes(Arrays.asList(TargetingLocation.LocationType.HOME)); + targeting.setExcludedGeoLocations(excludedGeoLocations); + // targeting cd. + targeting.setPageTypes(Arrays.asList(Targeting.PageType.DESKTOP_FEED, Targeting.PageType.MOBILE_FEED_AND_EXTERNAL)); + targeting.setConnections(Arrays.asList("123456789", "55442211")); + targeting.setExcludedConnections(Arrays.asList("33441122")); + targeting.setFriendsOfConnections(Arrays.asList("987654321")); + targeting.setInterests(Arrays.asList(new TargetingEntry(986123123123L, "Football"))); + targeting.setBehaviors(Arrays.asList(new TargetingEntry(1L, "Some behavior"))); + targeting.setEducationSchools(Arrays.asList(new TargetingEntry(10593123549L, "Poznan University of Technology"))); + targeting.setEducationStatuses(Arrays.asList(Targeting.EducationStatus.MASTER_DEGREE)); + targeting.setCollegeYears(Arrays.asList(Integer.valueOf(8))); + targeting.setEducationMajors(Arrays.asList(new TargetingEntry(12L, "Some major"))); + targeting.setWorkEmployers(Arrays.asList(new TargetingEntry(43125L, "Super company"))); + targeting.setWorkPositions(Arrays.asList(new TargetingEntry(11111L, "Developer"))); + adSet.setTargeting(targeting); + + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + adSet.setStartTime(formatter.parse("2015-05-27 18:00:00")); + adSet.setEndTime(formatter.parse("2015-06-27 17:59:59")); + + assertEquals("702123456789", facebookAds.adSetOperations().createAdSet("123456789", adSet)); + mockServer.verify(); + } +} diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set.json new file mode 100644 index 000000000..88619f964 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set.json @@ -0,0 +1,106 @@ +{ + "id": "700123456789", + "account_id": "123456789", + "campaign_group_id": "600123456789", + "name": "Test AdSet", + "campaign_status": "ACTIVE", + "is_autobid": true, + "bid_type": "ABSOLUTE_OCPM", + "budget_remaining": 50, + "daily_budget": 0, + "lifetime_budget": 200, + "targeting": { + "age_max": 20, + "age_min": 18, + "behaviors": [ + { + "id": "6004854404172", + "name": "Technology late adopters" + } + ], + "genders": [ + 1 + ], + "relationship_statuses": [ + 2, + 9 + ], + "interested_in": [ + 2 + ], + "geo_locations": { + "countries": [ + "PL" + ], + "regions": [ + { + "key": "3847" + }, + { + "key": "1122" + } + ], + "cities": [ + { + "key": "2430536", + "radius": 12, + "distance_unit": "mile" + }, + { + "key": "11223344", + "radius": 55, + "distance_unit": "kilometer" + } + ], + "zips": [ + { + "key": "US:94304" + }, + { + "key": "US:00501" + } + ], + "location_types": [ + "home", + "recent" + ] + }, + "interests": [ + { + "id": "6003629266583", + "name": "Hard drives" + } + ], + "page_types": [ + "feed", + "desktop-and-mobile-and-external" + ], + "education_schools": [ + { + "id": 105930651606, + "name": "Harvard University" + } + ], + "education_statuses": [ + 1, + 9, + 13 + ], + "work_employers": [ + { + "id": "50431654", + "name": "Microsoft" + } + ], + "work_positions": [ + { + "id": 105763692790962, + "name": "Business Analyst" + } + ] + }, + "start_time": "2015-04-12T09:19:00+0200", + "end_time": "2015-04-13T09:19:00+0200", + "created_time": "2015-04-10T09:28:54+0200", + "updated_time": "2015-04-10T13:32:09+0200" +} \ No newline at end of file From 119dffbb82efc1098c7f363e5fee621d3c8f6edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Thu, 28 May 2015 22:43:13 +0200 Subject: [PATCH 16/28] Adds implementation of update and delete operations for AdSet. --- .../facebook/api/ads/AdSetOperations.java | 29 +++++++++ .../facebook/api/ads/impl/AdSetTemplate.java | 49 +++++++++++---- .../facebook/api/ads/AdSetTemplateTest.java | 60 ++++++++++++++++++- 3 files changed, 124 insertions(+), 14 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java index 4b0c82c99..ff09b763e 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java @@ -1,5 +1,9 @@ package org.springframework.social.facebook.api.ads; +import org.springframework.social.ApiException; +import org.springframework.social.InsufficientPermissionException; +import org.springframework.social.MissingAuthorizationException; + /** * Defines operations for working with Facebook Ad set object. * @@ -17,6 +21,9 @@ public interface AdSetOperations { * * @param id the id of the ad set * @return the {@link AdSet} object + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ AdSet getAdSet(String id); @@ -26,6 +33,28 @@ public interface AdSetOperations { * @param accountId the account id * @param adSet the ad set object * @return the id of the new ad set. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ String createAdSet(String accountId, AdSet adSet); + + /** + * Updates the ad set. + * + * @param adSetId the id of the ad set + * @param adSet the ad set object + * @return true if update was successful + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + boolean updateAdSet(String adSetId, AdSet adSet); + + /** + * Deletes ad set given by id. + * + * @param adSetId the id of the ad set + */ + void deleteAdSet(String adSetId); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java index 2fc5842b9..ba8061e13 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java @@ -26,15 +26,39 @@ public AdSetTemplate(GraphApi graphApi, RestTemplate restTemplate, ObjectMapper } public AdSet getAdSet(String id) { + requireAuthorization(); return graphApi.fetchObject(id, AdSet.class, AdSetOperations.AD_SET_FIELDS); } public String createAdSet(String accountId, AdSet adSet) { + requireAuthorization(); + MultiValueMap data = mapCommonFields(adSet); + data.set("campaign_group_id", adSet.getCampaignId()); + return graphApi.publish(getAdAccountId(accountId), "adcampaigns", data); + } + + public boolean updateAdSet(String adSetId, AdSet adSet) { + requireAuthorization(); + MultiValueMap data = mapCommonFields(adSet); + return graphApi.update(adSetId, data); + } + + public void deleteAdSet(String adSetId) { + requireAuthorization(); + MultiValueMap data = new LinkedMultiValueMap(); + data.add("campaign_status", "DELETED"); + graphApi.post(adSetId, data); + } + + private MultiValueMap mapCommonFields(AdSet adSet) { MultiValueMap data = new LinkedMultiValueMap(); data.set("date_format", "U"); - data.set("campaign_group_id", adSet.getCampaignId()); - data.set("name", adSet.getName()); - data.set("campaign_status", adSet.getStatus().name()); + if (adSet.getName() != null) { + data.set("name", adSet.getName()); + } + if (adSet.getStatus() != null) { + data.set("campaign_status", adSet.getStatus().name()); + } data.set("is_autobid", String.valueOf(adSet.isAutobid())); if (adSet.getBidInfo() != null) { try { @@ -43,13 +67,17 @@ public String createAdSet(String accountId, AdSet adSet) { e.printStackTrace(); } } - data.set("bid_type", adSet.getBidType().name()); + if (adSet.getBidType() != null) { + data.set("bid_type", adSet.getBidType().name()); + } data.set("daily_budget", String.valueOf(adSet.getDailyBudget())); data.set("lifetime_budget", String.valueOf(adSet.getLifetimeBudget())); - try { - data.set("targeting", mapper.writeValueAsString(adSet.getTargeting())); - } catch (JsonProcessingException e) { - e.printStackTrace(); + if (adSet.getTargeting() != null) { + try { + data.set("targeting", mapper.writeValueAsString(adSet.getTargeting())); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } } if (adSet.getStartTime() != null) { data.set("start_time", getUnixTime(adSet.getStartTime())); @@ -57,7 +85,6 @@ public String createAdSet(String accountId, AdSet adSet) { if (adSet.getEndTime() != null) { data.set("end_time", getUnixTime(adSet.getEndTime())); } - - return graphApi.publish(getAdAccountId(accountId), "adcampaigns", data); + return data; } -} +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index 283a36d5d..2e3872fa0 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -2,6 +2,7 @@ import org.junit.Test; import org.springframework.http.MediaType; +import org.springframework.social.NotAuthorizedException; import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; import org.springframework.social.facebook.api.ads.AdSet.BidType; @@ -98,9 +99,14 @@ public void getAdSet() throws Exception { assertEquals(toDate("2015-04-10T13:32:09+0200"), adSet.getUpdatedTime()); } + @Test(expected = NotAuthorizedException.class) + public void getAdSet_unauthorized() throws Exception { + unauthorizedFacebookAds.adSetOperations().getAdSet("700123456789"); + } + @Test public void createAdSet() throws Exception { - String requestBody = "date_format=U&campaign_group_id=600123456789&name=Test+AdSet&campaign_status=PAUSED&is_autobid=true&bid_type=ABSOLUTE_OCPM&daily_budget=0&lifetime_budget=200&targeting=%7B%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%5D%7D%7D&end_time=1432231200"; + String requestBody = "date_format=U&name=Test+AdSet&campaign_status=PAUSED&is_autobid=true&bid_type=ABSOLUTE_OCPM&daily_budget=0&lifetime_budget=200&targeting=%7B%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%5D%7D%7D&end_time=1432231200&campaign_group_id=600123456789"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) @@ -127,11 +133,11 @@ public void createAdSet() throws Exception { @Test public void createAdSet_withAllFields() throws Exception { - String requestBody = "date_format=U&campaign_group_id=601123456789&name=Test+AdSet+2&campaign_status=ACTIVE&is_autobid=false&" + + String requestBody = "date_format=U&name=Test+AdSet+2&campaign_status=ACTIVE&is_autobid=false&" + "bid_info=%7B%22REACH%22%3A%221000%22%2C%22ACTIONS%22%3A%22200%22%2C%22SOCIAL%22%3A%22110%22%2C%22CLICKS%22%3A%22500%22%7D&" + "bid_type=ABSOLUTE_OCPM&daily_budget=4000&lifetime_budget=0&" + "targeting=%7B%22genders%22%3A%5B1%2C2%5D%2C%22age_min%22%3A45%2C%22age_max%22%3A55%2C%22relationship_statuses%22%3A%5B10%2C12%5D%2C%22interested_in%22%3A%5B1%2C2%5D%2C%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%2C%22DE%22%2C%22US%22%2C%22FR%22%5D%2C%22regions%22%3A%5B%7B%22key%22%3A%223847%22%7D%2C%7B%22key%22%3A%221111%22%7D%2C%7B%22key%22%3A%221234%22%7D%2C%7B%22key%22%3A%229888%22%7D%5D%2C%22cities%22%3A%5B%7B%22key%22%3A%222430536%22%2C%22radius%22%3A12%2C%22distance_unit%22%3A%22mile%22%7D%2C%7B%22key%22%3A%22777555%22%2C%22radius%22%3A1024%2C%22distance_unit%22%3A%22kilometer%22%7D%5D%2C%22zips%22%3A%5B%7B%22key%22%3A%22PL%3A62030%22%7D%2C%7B%22key%22%3A%22US%3A88123%22%7D%2C%7B%22key%22%3A%22FR%3A33144%22%7D%5D%2C%22location_types%22%3A%5B%22home%22%2C%22recent%22%5D%7D%2C%22excluded_geo_locations%22%3A%7B%22countries%22%3A%5B%22HU%22%2C%22JP%22%5D%2C%22regions%22%3A%5B%7B%22key%22%3A%221122%22%7D%2C%7B%22key%22%3A%2231415%22%7D%5D%2C%22cities%22%3A%5B%7B%22key%22%3A%2288997766%22%2C%22radius%22%3A12345%2C%22distance_unit%22%3A%22mile%22%7D%5D%2C%22zips%22%3A%5B%7B%22key%22%3A%22JP%3A44552%22%7D%5D%2C%22location_types%22%3A%5B%22home%22%5D%7D%2C%22page_types%22%3A%5B%22desktopfeed%22%2C%22mobilefeed-and-external%22%5D%2C%22connections%22%3A%5B%22123456789%22%2C%2255442211%22%5D%2C%22excluded_connections%22%3A%5B%2233441122%22%5D%2C%22friends_of_connections%22%3A%5B%22987654321%22%5D%2C%22interests%22%3A%5B%7B%22id%22%3A986123123123%2C%22name%22%3A%22Football%22%7D%5D%2C%22behaviors%22%3A%5B%7B%22id%22%3A1%2C%22name%22%3A%22Some+behavior%22%7D%5D%2C%22education_schools%22%3A%5B%7B%22id%22%3A10593123549%2C%22name%22%3A%22Poznan+University+of+Technology%22%7D%5D%2C%22education_statuses%22%3A%5B9%5D%2C%22college_years%22%3A%5B8%5D%2C%22education_majors%22%3A%5B%7B%22id%22%3A12%2C%22name%22%3A%22Some+major%22%7D%5D%2C%22work_employers%22%3A%5B%7B%22id%22%3A43125%2C%22name%22%3A%22Super+company%22%7D%5D%2C%22work_positions%22%3A%5B%7B%22id%22%3A11111%2C%22name%22%3A%22Developer%22%7D%5D%7D&" + - "start_time=1432742400&end_time=1435420799"; + "start_time=1432742400&end_time=1435420799&campaign_group_id=601123456789"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) @@ -196,4 +202,52 @@ public void createAdSet_withAllFields() throws Exception { assertEquals("702123456789", facebookAds.adSetOperations().createAdSet("123456789", adSet)); mockServer.verify(); } + + @Test(expected = NotAuthorizedException.class) + public void createAdSet_unauthorized() throws Exception { + unauthorizedFacebookAds.adSetOperations().createAdSet("123456789", new AdSet()); + } + + @Test + public void updateAdSet() throws Exception { + String requestBody = "date_format=U&name=New+AdSet+name&campaign_status=ARCHIVED&is_autobid=true&daily_budget=0&lifetime_budget=50000&start_time=1432833720"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/700123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + + AdSet adSet = new AdSet(); + adSet.setName("New AdSet name"); + adSet.setStatus(AdSetStatus.ARCHIVED); + adSet.setLifetimeBudget(50000); + adSet.setAutobid(true); + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + adSet.setStartTime(formatter.parse("2015-05-28 19:22:00")); + + assertTrue(facebookAds.adSetOperations().updateAdSet("700123456789", adSet)); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAdSet_unauthorized() throws Exception { + unauthorizedFacebookAds.adSetOperations().updateAdSet("700123456789", new AdSet()); + } + + @Test + public void deleteAdSet() throws Exception { + String requestBody = "campaign_status=DELETED"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/700123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); + facebookAds.adSetOperations().deleteAdSet("700123456789"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void deleteAdSet_unauthorized() throws Exception { + unauthorizedFacebookAds.adSetOperations().deleteAdSet("700123456789"); + } } From 787f7c1a333a7c54ae246e3fec7a465f87f0de8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Fri, 29 May 2015 13:48:18 +0200 Subject: [PATCH 17/28] Adds some method for accessing AdSets from different templates. --- .../facebook/api/ads/AdSetOperations.java | 30 ++++++- .../facebook/api/ads/CampaignOperations.java | 11 +++ .../facebook/api/ads/impl/AdSetTemplate.java | 11 +++ .../api/ads/impl/CampaignTemplate.java | 7 ++ .../facebook/api/ads/AdSetTemplateTest.java | 86 ++++++++++++++++++- .../api/ads/CampaignTemplateTest.java | 62 +++++++++++++ .../social/facebook/api/ads/ad-sets.json | 85 ++++++++++++++++++ 7 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-sets.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java index ff09b763e..40ce33ea1 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java @@ -3,6 +3,7 @@ import org.springframework.social.ApiException; import org.springframework.social.InsufficientPermissionException; import org.springframework.social.MissingAuthorizationException; +import org.springframework.social.facebook.api.PagedList; /** * Defines operations for working with Facebook Ad set object. @@ -16,6 +17,28 @@ public interface AdSetOperations { "start_time", "targeting", "updated_time" }; + /** + * Gets all ad sets from ad account given by account id. + * + * @param accountId the ID of an ad account + * @return the list of {@link AdSet} objects + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getAdSets(String accountId); + + /** + * Get all ad sets for the given campaign + * + * @param campaignId the id of the ad campaing + * @return the list of {@link AdSet} objects. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getCampaignAdSets(String campaignId); + /** * Gets ad set by given id. * @@ -42,8 +65,8 @@ public interface AdSetOperations { /** * Updates the ad set. * - * @param adSetId the id of the ad set - * @param adSet the ad set object + * @param adSetId the id of the ad set + * @param adSet the ad set object * @return true if update was successful * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. @@ -55,6 +78,9 @@ public interface AdSetOperations { * Deletes ad set given by id. * * @param adSetId the id of the ad set + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ void deleteAdSet(String adSetId); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java index 55460d2f9..21f2d58c2 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java @@ -39,6 +39,17 @@ public interface CampaignOperations { */ AdCampaign getAdCampaign(String id); + /** + * Get all ad sets from one ad campaign. + * + * @param campaignId the id of the campaign + * @return the list of {@link AdSet} objects + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getAdCampaignSets(String campaignId); + /** * Creates new campaign based on adCampaign object. * diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java index ba8061e13..472fc144c 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.social.facebook.api.GraphApi; +import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AdSet; import org.springframework.social.facebook.api.ads.AdSetOperations; import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; @@ -25,6 +26,16 @@ public AdSetTemplate(GraphApi graphApi, RestTemplate restTemplate, ObjectMapper this.mapper = mapper; } + public PagedList getAdSets(String accountId) { + requireAuthorization(); + return graphApi.fetchConnections(getAdAccountId(accountId), "adcampaigns", AdSet.class, AdSetOperations.AD_SET_FIELDS); + } + + public PagedList getCampaignAdSets(String campaignId) { + requireAuthorization(); + return graphApi.fetchConnections(campaignId, "adcampaigns", AdSet.class, AdSetOperations.AD_SET_FIELDS); + } + public AdSet getAdSet(String id) { requireAuthorization(); return graphApi.fetchObject(id, AdSet.class, AdSetOperations.AD_SET_FIELDS); diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java index 0f18e930d..b86469dee 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java @@ -4,6 +4,8 @@ import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AdCampaign; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; +import org.springframework.social.facebook.api.ads.AdSet; +import org.springframework.social.facebook.api.ads.AdSetOperations; import org.springframework.social.facebook.api.ads.CampaignOperations; import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; import org.springframework.util.LinkedMultiValueMap; @@ -35,6 +37,11 @@ public AdCampaign getAdCampaign(String id) { return graphApi.fetchObject(id, AdCampaign.class, CampaignOperations.AD_CAMPAIGN_FIELDS); } + public PagedList getAdCampaignSets(String campaignId) { + requireAuthorization(); + return graphApi.fetchConnections(campaignId, "adcampaigns", AdSet.class, AdSetOperations.AD_SET_FIELDS); + } + public String createAdCampaign(String accountId, AdCampaign adCampaign) { requireAuthorization(); MultiValueMap map = mapCommonFields(adCampaign); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index 2e3872fa0..da4d3f2ea 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -3,15 +3,13 @@ import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.social.NotAuthorizedException; +import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; import org.springframework.social.facebook.api.ads.AdSet.BidType; -import java.lang.reflect.Array; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -25,6 +23,40 @@ */ public class AdSetTemplateTest extends AbstractFacebookAdsApiTest { + @Test + public void getAdSets() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-sets"), MediaType.APPLICATION_JSON)); + + PagedList adSets = facebookAds.adSetOperations().getAdSets("123456789"); + verifyAdSets(adSets); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdSets_unauthorized() throws Exception { + unauthorizedFacebookAds.adSetOperations().getAdSets("123456789"); + } + + @Test + public void getCampaignAdsets() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789/adcampaigns?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-sets"), MediaType.APPLICATION_JSON)); + + PagedList adSets = facebookAds.adSetOperations().getCampaignAdSets("600123456789"); + verifyAdSets(adSets); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getCampaignAdSets_unauthorized() throws Exception { + unauthorizedFacebookAds.adSetOperations().getCampaignAdSets("600123456789"); + } + @Test public void getAdSet() throws Exception { mockServer.expect(requestTo("https://graph.facebook.com/v2.3/700123456789?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) @@ -250,4 +282,52 @@ public void deleteAdSet() throws Exception { public void deleteAdSet_unauthorized() throws Exception { unauthorizedFacebookAds.adSetOperations().deleteAdSet("700123456789"); } + + private void verifyAdSets(PagedList adSets) { + assertEquals(2, adSets.size()); + assertEquals("123456789", adSets.get(0).getAccountId()); + assertEquals(BidType.ABSOLUTE_OCPM, adSets.get(0).getBidType()); + assertEquals(37407, adSets.get(0).getBudgetRemaining()); + assertEquals("600123456789", adSets.get(0).getCampaignId()); + assertEquals(AdSetStatus.PAUSED, adSets.get(0).getStatus()); + assertEquals(toDate("2015-05-27T11:58:34+0200"), adSets.get(0).getCreatedTime()); + assertEquals(40000, adSets.get(0).getDailyBudget()); + assertEquals(toDate("2015-05-29T22:26:40+0200"), adSets.get(0).getEndTime()); + assertEquals("700123456789", adSets.get(0).getId()); + assertTrue(adSets.get(0).isAutobid()); + assertEquals(0, adSets.get(0).getLifetimeBudget()); + assertEquals("Test AdSet", adSets.get(0).getName()); + assertEquals(toDate("2015-05-27T11:58:34+0200"), adSets.get(0).getStartTime()); + assertEquals(Integer.valueOf(65), adSets.get(0).getTargeting().getAgeMax()); + assertEquals(Integer.valueOf(18), adSets.get(0).getTargeting().getAgeMin()); + assertEquals("BR", adSets.get(0).getTargeting().getGeoLocations().getCountries().get(0)); + assertEquals(TargetingLocation.LocationType.HOME, adSets.get(0).getTargeting().getGeoLocations().getLocationTypes().get(0)); + assertEquals(toDate("2015-05-27T11:58:34+0200"), adSets.get(0).getUpdatedTime()); + + assertEquals("123456789", adSets.get(1).getAccountId()); + assertEquals(BidType.ABSOLUTE_OCPM, adSets.get(1).getBidType()); + assertEquals(0, adSets.get(1).getBudgetRemaining()); + assertEquals("600123456789", adSets.get(1).getCampaignId()); + assertEquals(AdSetStatus.ACTIVE, adSets.get(1).getStatus()); + assertEquals(toDate("2015-04-10T09:28:54+0200"), adSets.get(1).getCreatedTime()); + assertEquals(0, adSets.get(1).getDailyBudget()); + assertEquals(toDate("2015-04-13T09:19:00+0200"), adSets.get(1).getEndTime()); + assertEquals("701123456789", adSets.get(1).getId()); + assertTrue(adSets.get(1).isAutobid()); + assertEquals(200, adSets.get(1).getLifetimeBudget()); + assertEquals("Real ad set", adSets.get(1).getName()); + assertEquals(toDate("2015-04-12T09:19:00+0200"), adSets.get(1).getStartTime()); + assertEquals(Integer.valueOf(20), adSets.get(1).getTargeting().getAgeMax()); + assertEquals(Integer.valueOf(18), adSets.get(1).getTargeting().getAgeMin()); + assertEquals(6004854404172L, adSets.get(1).getTargeting().getBehaviors().get(0).getId()); + assertEquals("Technology late adopters", adSets.get(1).getTargeting().getBehaviors().get(0).getName()); + assertEquals(Targeting.Gender.MALE, adSets.get(1).getTargeting().getGenders().get(0)); + assertEquals("PL", adSets.get(1).getTargeting().getGeoLocations().getCountries().get(0)); + assertEquals(TargetingLocation.LocationType.HOME, adSets.get(1).getTargeting().getGeoLocations().getLocationTypes().get(0)); + assertEquals(TargetingLocation.LocationType.RECENT, adSets.get(1).getTargeting().getGeoLocations().getLocationTypes().get(1)); + assertEquals(6003629266583L, adSets.get(1).getTargeting().getInterests().get(0).getId()); + assertEquals("Hard drives", adSets.get(1).getTargeting().getInterests().get(0).getName()); + assertEquals(Targeting.PageType.FEED, adSets.get(1).getTargeting().getPageTypes().get(0)); + assertEquals(toDate("2015-04-10T13:32:09+0200"), adSets.get(1).getUpdatedTime()); + } } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index 47f7ace71..b7db0391f 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -101,6 +101,68 @@ public void getCampaign_unauthorized() throws Exception { unauthorizedFacebookAds.campaignOperations().getAdCampaign("600123456789"); } + @Test + public void getAdCampaignSets() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789/adcampaigns?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-sets"), MediaType.APPLICATION_JSON)); + + PagedList adSets = facebookAds.campaignOperations().getAdCampaignSets("600123456789"); + assertEquals(2, adSets.size()); + assertEquals("123456789", adSets.get(0).getAccountId()); + assertEquals(AdSet.BidType.ABSOLUTE_OCPM, adSets.get(0).getBidType()); + assertEquals(37407, adSets.get(0).getBudgetRemaining()); + assertEquals("600123456789", adSets.get(0).getCampaignId()); + assertEquals(AdSet.AdSetStatus.PAUSED, adSets.get(0).getStatus()); + assertEquals(toDate("2015-05-27T11:58:34+0200"), adSets.get(0).getCreatedTime()); + assertEquals(40000, adSets.get(0).getDailyBudget()); + assertEquals(toDate("2015-05-29T22:26:40+0200"), adSets.get(0).getEndTime()); + assertEquals("700123456789", adSets.get(0).getId()); + assertTrue(adSets.get(0).isAutobid()); + assertEquals(0, adSets.get(0).getLifetimeBudget()); + assertEquals("Test AdSet", adSets.get(0).getName()); + assertEquals(toDate("2015-05-27T11:58:34+0200"), adSets.get(0).getStartTime()); + assertEquals(Integer.valueOf(65), adSets.get(0).getTargeting().getAgeMax()); + assertEquals(Integer.valueOf(18), adSets.get(0).getTargeting().getAgeMin()); + assertEquals("BR", adSets.get(0).getTargeting().getGeoLocations().getCountries().get(0)); + assertEquals(TargetingLocation.LocationType.HOME, adSets.get(0).getTargeting().getGeoLocations().getLocationTypes().get(0)); + assertEquals(toDate("2015-05-27T11:58:34+0200"), adSets.get(0).getUpdatedTime()); + + assertEquals("123456789", adSets.get(1).getAccountId()); + assertEquals(AdSet.BidType.ABSOLUTE_OCPM, adSets.get(1).getBidType()); + assertEquals(0, adSets.get(1).getBudgetRemaining()); + assertEquals("601123456789", adSets.get(1).getCampaignId()); + assertEquals(AdSet.AdSetStatus.ACTIVE, adSets.get(1).getStatus()); + assertEquals(toDate("2015-04-10T09:28:54+0200"), adSets.get(1).getCreatedTime()); + assertEquals(0, adSets.get(1).getDailyBudget()); + assertEquals(toDate("2015-04-13T09:19:00+0200"), adSets.get(1).getEndTime()); + assertEquals("701123456789", adSets.get(1).getId()); + assertTrue(adSets.get(1).isAutobid()); + assertEquals(200, adSets.get(1).getLifetimeBudget()); + assertEquals("Real ad set", adSets.get(1).getName()); + assertEquals(toDate("2015-04-12T09:19:00+0200"), adSets.get(1).getStartTime()); + assertEquals(Integer.valueOf(20), adSets.get(1).getTargeting().getAgeMax()); + assertEquals(Integer.valueOf(18), adSets.get(1).getTargeting().getAgeMin()); + assertEquals(6004854404172L, adSets.get(1).getTargeting().getBehaviors().get(0).getId()); + assertEquals("Technology late adopters", adSets.get(1).getTargeting().getBehaviors().get(0).getName()); + assertEquals(Targeting.Gender.MALE, adSets.get(1).getTargeting().getGenders().get(0)); + assertEquals("PL", adSets.get(1).getTargeting().getGeoLocations().getCountries().get(0)); + assertEquals(TargetingLocation.LocationType.HOME, adSets.get(1).getTargeting().getGeoLocations().getLocationTypes().get(0)); + assertEquals(TargetingLocation.LocationType.RECENT, adSets.get(1).getTargeting().getGeoLocations().getLocationTypes().get(1)); + assertEquals(6003629266583L, adSets.get(1).getTargeting().getInterests().get(0).getId()); + assertEquals("Hard drives", adSets.get(1).getTargeting().getInterests().get(0).getName()); + assertEquals(Targeting.PageType.FEED, adSets.get(1).getTargeting().getPageTypes().get(0)); + assertEquals(toDate("2015-04-10T13:32:09+0200"), adSets.get(1).getUpdatedTime()); + + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdCampaignSets_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().getAdCampaignSets("600123456789"); + } + @Test public void createCampaign_withNameOnly() throws Exception { String requestBody = "name=Campaign+created+by+SpringSocialFacebook"; diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-sets.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-sets.json new file mode 100644 index 000000000..306032dcf --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-sets.json @@ -0,0 +1,85 @@ +{ + "data": [ + { + "account_id": "123456789", + "bid_type": "ABSOLUTE_OCPM", + "budget_remaining": 37407, + "campaign_group_id": "600123456789", + "campaign_status": "PAUSED", + "created_time": "2015-05-27T11:58:34+0200", + "daily_budget": 40000, + "end_time": "2015-05-29T22:26:40+0200", + "id": "700123456789", + "is_autobid": true, + "lifetime_budget": 0, + "name": "Test AdSet", + "start_time": "2015-05-27T11:58:34+0200", + "targeting": { + "age_max": 65, + "age_min": 18, + "geo_locations": { + "countries": [ + "BR" + ], + "location_types": [ + "home" + ] + } + }, + "updated_time": "2015-05-27T11:58:34+0200" + }, + { + "account_id": "123456789", + "bid_type": "ABSOLUTE_OCPM", + "budget_remaining": 0, + "campaign_group_id": "600123456789", + "campaign_status": "ACTIVE", + "created_time": "2015-04-10T09:28:54+0200", + "daily_budget": 0, + "end_time": "2015-04-13T09:19:00+0200", + "id": "701123456789", + "is_autobid": true, + "lifetime_budget": 200, + "name": "Real ad set", + "start_time": "2015-04-12T09:19:00+0200", + "targeting": { + "age_max": 20, + "age_min": 18, + "behaviors": [ + { + "id": "6004854404172", + "name": "Technology late adopters" + } + ], + "genders": [ + 1 + ], + "geo_locations": { + "countries": [ + "PL" + ], + "location_types": [ + "home", + "recent" + ] + }, + "interests": [ + { + "id": "6003629266583", + "name": "Hard drives" + } + ], + "page_types": [ + "feed" + ] + }, + "updated_time": "2015-04-10T13:32:09+0200" + } + ], + "paging": { + "cursors": { + "before": "NjAyNDE2Nzg5NTY1MA==", + "after": "NjAyNTIwMDQ1MTQ1MA==" + } + } +} \ No newline at end of file From bfa9ec8074142997b0540007bd4b61353457866b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Mon, 1 Jun 2015 12:10:45 +0200 Subject: [PATCH 18/28] Adds method for getting info about ad set insights. --- .../facebook/api/ads/AdSetOperations.java | 20 ++++ .../facebook/api/ads/impl/AdSetTemplate.java | 7 ++ .../facebook/api/ads/AdSetTemplateTest.java | 82 ++++++++++++++ .../facebook/api/ads/ad-set-insights.json | 105 ++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-insights.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java index 40ce33ea1..3a60d581e 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java @@ -17,6 +17,15 @@ public interface AdSetOperations { "start_time", "targeting", "updated_time" }; + static final String[] AD_SET_INSIGHT_FIELDS = { + "account_id", "account_name", "date_start", "date_stop", "actions_per_impression", "clicks", "unique_clicks", + "cost_per_result", "cost_per_total_action", "cpc", "cost_per_unique_click", "cpm", "cpp", "ctr", "unique_ctr", + "frequency", "impressions", "unique_impressions", "objective", "reach", "result_rate", "results", "roas", + "social_clicks", "unique_social_clicks", "social_impressions", "unique_social_impressions", "social_reach", + "spend", "today_spend", "total_action_value", "total_actions", "total_unique_actions", "actions", + "unique_actions", "cost_per_action_type", "video_start_actions" + }; + /** * Gets all ad sets from ad account given by account id. * @@ -50,6 +59,17 @@ public interface AdSetOperations { */ AdSet getAdSet(String id); + /** + * Get the insight for the ad set. + * + * @param adSetId the id of the ad set + * @return the {@ink Insight} object + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + AdInsight getAdSetInsight(String adSetId); + /** * Creates an ad set in the given account * diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java index 472fc144c..2244440f5 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.social.facebook.api.GraphApi; import org.springframework.social.facebook.api.PagedList; +import org.springframework.social.facebook.api.ads.AdInsight; import org.springframework.social.facebook.api.ads.AdSet; import org.springframework.social.facebook.api.ads.AdSetOperations; import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; @@ -41,6 +42,12 @@ public AdSet getAdSet(String id) { return graphApi.fetchObject(id, AdSet.class, AdSetOperations.AD_SET_FIELDS); } + public AdInsight getAdSetInsight(String adSetId) { + requireAuthorization(); + PagedList insights = graphApi.fetchConnections(adSetId, "insights", AdInsight.class, AdSetOperations.AD_SET_INSIGHT_FIELDS); + return insights.get(0); + } + public String createAdSet(String accountId, AdSet adSet) { requireAuthorization(); MultiValueMap data = mapCommonFields(adSet); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index da4d3f2ea..d73d8f046 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -23,6 +23,8 @@ */ public class AdSetTemplateTest extends AbstractFacebookAdsApiTest { + private static final double EPSILON = 0.000000000001; + @Test public void getAdSets() throws Exception { mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) @@ -136,6 +138,86 @@ public void getAdSet_unauthorized() throws Exception { unauthorizedFacebookAds.adSetOperations().getAdSet("700123456789"); } + @Test + public void getAdSetInsights() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/700123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions")) + + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-set-insights"), MediaType.APPLICATION_JSON)); + + AdInsight insight = facebookAds.adSetOperations().getAdSetInsight("700123456789"); + + assertEquals("123456789", insight.getAccountId()); + assertEquals("Test account name", insight.getAccountName()); + assertEquals(0.016042780748663, insight.getActionsPerImpression(), EPSILON); + assertEquals(8, insight.getClicks()); + assertEquals(5, insight.getUniqueClicks()); + assertEquals(0.66666666666667, insight.getCostPerResult(), EPSILON); + assertEquals(0.66666666666667, insight.getCostPerTotalAction(), EPSILON); + assertEquals(0.25, insight.getCostPerClick(), EPSILON); + assertEquals(0.4, insight.getCostPerUniqueClick(), EPSILON); + assertEquals(10.695187165775, insight.getCpm(), EPSILON); + assertEquals(10.869565217391, insight.getCpp(), EPSILON); + assertEquals(4.2780748663102, insight.getCtr(), EPSILON); + assertEquals(2.7173913043478, insight.getUniqueCtr(), EPSILON); + assertEquals(1.0163043478261, insight.getFrequency(), EPSILON); + assertEquals(187, insight.getImpressions()); + assertEquals(184, insight.getUniqueImpressions()); + assertEquals(184, insight.getReach()); + assertEquals(1.6042780748663, insight.getResultRate(), EPSILON); + assertEquals(3, insight.getResults()); + assertEquals(0, insight.getRoas()); + assertEquals(0, insight.getSocialClicks()); + assertEquals(0, insight.getUniqueSocialClicks()); + assertEquals(0, insight.getSocialImpressions()); + assertEquals(0, insight.getUniqueSocialImpressions()); + assertEquals(0, insight.getSocialReach()); + assertEquals(2, insight.getSpend()); + assertEquals(0, insight.getTodaySpend()); + assertEquals(0, insight.getTotalActionValue()); + assertEquals(3, insight.getTotalActions()); + assertEquals(2, insight.getTotalUniqueActions()); + assertEquals(4, insight.getActions().size()); + assertEquals("comment", insight.getActions().get(0).getActionType()); + assertEquals(2, insight.getActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getActions().get(1).getActionType()); + assertEquals(1, insight.getActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getActions().get(2).getActionType()); + assertEquals(3, insight.getActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getActions().get(3).getActionType()); + assertEquals(3, insight.getActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getUniqueActions().size()); + assertEquals("comment", insight.getUniqueActions().get(0).getActionType()); + assertEquals(1, insight.getUniqueActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getUniqueActions().get(1).getActionType()); + assertEquals(1, insight.getUniqueActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getUniqueActions().get(2).getActionType()); + assertEquals(2, insight.getUniqueActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getUniqueActions().get(3).getActionType()); + assertEquals(2, insight.getUniqueActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getCostPerActionType().size()); + assertEquals("comment", insight.getCostPerActionType().get(0).getActionType()); + assertEquals(1, insight.getCostPerActionType().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getCostPerActionType().get(1).getActionType()); + assertEquals(2, insight.getCostPerActionType().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getCostPerActionType().get(2).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getCostPerActionType().get(3).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(3).getValue(), EPSILON); + assertEquals(1, insight.getVideoStartActions().size()); + assertEquals("video_view", insight.getVideoStartActions().get(0).getActionType()); + assertEquals(0, insight.getVideoStartActions().get(0).getValue(), EPSILON); + + + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdSetInsights_unauthorized() throws Exception { + unauthorizedFacebookAds.adSetOperations().getAdSetInsight("700123456789"); + } + @Test public void createAdSet() throws Exception { String requestBody = "date_format=U&name=Test+AdSet&campaign_status=PAUSED&is_autobid=true&bid_type=ABSOLUTE_OCPM&daily_budget=0&lifetime_budget=200&targeting=%7B%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%5D%7D%7D&end_time=1432231200&campaign_group_id=600123456789"; diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-insights.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-insights.json new file mode 100644 index 000000000..2b40792de --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-insights.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "account_id": "123456789", + "account_name": "Test account name", + "date_start": "2015-04-12", + "date_stop": "2015-04-13", + "actions_per_impression": 0.016042780748663, + "clicks": 8, + "unique_clicks": 5, + "cost_per_result": 0.66666666666667, + "cost_per_total_action": 0.66666666666667, + "cpc": 0.25, + "cost_per_unique_click": 0.4, + "cpm": 10.695187165775, + "cpp": 10.869565217391, + "ctr": 4.2780748663102, + "unique_ctr": 2.7173913043478, + "frequency": 1.0163043478261, + "impressions": "187", + "unique_impressions": 184, + "objective": "POST_ENGAGEMENT", + "reach": 184, + "result_rate": 1.6042780748663, + "results": 3, + "roas": 0, + "social_clicks": 0, + "unique_social_clicks": 0, + "social_impressions": 0, + "unique_social_impressions": 0, + "social_reach": 0, + "spend": 2, + "today_spend": 0, + "total_action_value": 0, + "total_actions": 3, + "total_unique_actions": 2, + "actions": [ + { + "action_type": "comment", + "value": 2 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 3 + }, + { + "action_type": "post_engagement", + "value": 3 + } + ], + "unique_actions": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 2 + }, + { + "action_type": "post_engagement", + "value": 2 + } + ], + "cost_per_action_type": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 2 + }, + { + "action_type": "page_engagement", + "value": 0.66666666666667 + }, + { + "action_type": "post_engagement", + "value": 0.66666666666667 + } + ], + "video_start_actions": [ + { + "action_type": "video_view", + "value": 0 + } + ] + } + ], + "paging": { + "cursors": { + "before": "MA==", + "after": "MA==" + } + } +} \ No newline at end of file From e0ad3cefbbaf780ec5801df27de744dd601429c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Mon, 1 Jun 2015 12:25:00 +0200 Subject: [PATCH 19/28] Adds method for accessing ad campaign insights. --- .../facebook/api/ads/CampaignOperations.java | 20 ++++ .../api/ads/impl/CampaignTemplate.java | 11 +- .../api/ads/AbstractFacebookAdsApiTest.java | 1 + .../facebook/api/ads/AccountTemplateTest.java | 1 - .../facebook/api/ads/AdSetTemplateTest.java | 4 - .../api/ads/CampaignTemplateTest.java | 79 ++++++++++++- .../api/ads/ad-campaign-insights.json | 105 ++++++++++++++++++ 7 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-insights.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java index 21f2d58c2..acf739a1f 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java @@ -17,6 +17,15 @@ public interface CampaignOperations { "id", "account_id", "buying_type", "campaign_group_status", "name", "objective", "spend_cap" }; + static final String[] AD_CAMPAIGN_INSIGHT_FIELDS = { + "account_id", "account_name", "date_start", "date_stop", "actions_per_impression", "clicks", "unique_clicks", + "cost_per_result", "cost_per_total_action", "cpc", "cost_per_unique_click", "cpm", "cpp", "ctr", "unique_ctr", + "frequency", "impressions", "unique_impressions", "objective", "reach", "result_rate", "results", "roas", + "social_clicks", "unique_social_clicks", "social_impressions", "unique_social_impressions", "social_reach", + "spend", "today_spend", "total_action_value", "total_actions", "total_unique_actions", "actions", + "unique_actions", "cost_per_action_type", "video_start_actions" + }; + /** * Gets all ad campaigns belonging to user. * @@ -50,6 +59,17 @@ public interface CampaignOperations { */ PagedList getAdCampaignSets(String campaignId); + /** + * Get the insight of the ad campaign. + * + * @param campaignId the id of the campaign + * @return the {@link AdInsight} object + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + AdInsight getAdCampaignInsight(String campaignId); + /** * Creates new campaign based on adCampaign object. * diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java index b86469dee..b93040468 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java @@ -2,11 +2,8 @@ import org.springframework.social.facebook.api.GraphApi; import org.springframework.social.facebook.api.PagedList; -import org.springframework.social.facebook.api.ads.AdCampaign; +import org.springframework.social.facebook.api.ads.*; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; -import org.springframework.social.facebook.api.ads.AdSet; -import org.springframework.social.facebook.api.ads.AdSetOperations; -import org.springframework.social.facebook.api.ads.CampaignOperations; import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -42,6 +39,12 @@ public PagedList getAdCampaignSets(String campaignId) { return graphApi.fetchConnections(campaignId, "adcampaigns", AdSet.class, AdSetOperations.AD_SET_FIELDS); } + public AdInsight getAdCampaignInsight(String campaignId) { + requireAuthorization(); + PagedList insights = graphApi.fetchConnections(campaignId, "insights", AdInsight.class, CampaignOperations.AD_CAMPAIGN_INSIGHT_FIELDS); + return insights.get(0); + } + public String createAdCampaign(String accountId, AdCampaign adCampaign) { requireAuthorization(); MultiValueMap map = mapCommonFields(adCampaign); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AbstractFacebookAdsApiTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AbstractFacebookAdsApiTest.java index 37ec63c42..0edd3e18f 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AbstractFacebookAdsApiTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AbstractFacebookAdsApiTest.java @@ -18,6 +18,7 @@ public class AbstractFacebookAdsApiTest { protected static final String ACCESS_TOKEN = "someAccessToken"; private static final DateFormat FB_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ENGLISH); + protected static final double EPSILON = 0.000000000001; protected FacebookAdsTemplate facebookAds; protected FacebookAdsTemplate unauthorizedFacebookAds; protected MockRestServiceServer mockServer; diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java index d07f72d67..49099db09 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java @@ -31,7 +31,6 @@ public class AccountTemplateTest extends AbstractFacebookAdsApiTest { private static final String GET_ADACCOUNTS_REQUEST_URI = "https://graph.facebook.com/v2.3/1234/adaccounts?fields=id%2Caccount_id%2Caccount_status%2Cage%2Camount_spent%2Cbalance%2Cbusiness_city%2Cbusiness_country_code%2Cbusiness_name%2Cbusiness_state%2Cbusiness_street%2Cbusiness_street2%2Cbusiness_zip%2Ccapabilities%2Ccreated_time%2Ccurrency%2Cdaily_spend_limit%2Cend_advertiser%2Cfunding_source%2Cfunding_source_details%2Cis_personal%2Cmedia_agency%2Cname%2Coffsite_pixels_tos_accepted%2Cpartner%2Cspend_cap%2Ctimezone_id%2Ctimezone_name%2Ctimezone_offset_hours_utc%2Cusers%2Ctax_id_status"; private static final String GET_ADACCOUNT_USERS_REQUEST_URI = "https://graph.facebook.com/v2.3/act_123456789/users"; private static final String GET_ADACCOUNT_INSIGHT = "https://graph.facebook.com/v2.3/act_123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions"; - private static final double EPSILON = 0.000000000001; @Test diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index d73d8f046..ea9399a08 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -23,8 +23,6 @@ */ public class AdSetTemplateTest extends AbstractFacebookAdsApiTest { - private static final double EPSILON = 0.000000000001; - @Test public void getAdSets() throws Exception { mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) @@ -141,7 +139,6 @@ public void getAdSet_unauthorized() throws Exception { @Test public void getAdSetInsights() throws Exception { mockServer.expect(requestTo("https://graph.facebook.com/v2.3/700123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions")) - .andExpect(method(GET)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-set-insights"), MediaType.APPLICATION_JSON)); @@ -209,7 +206,6 @@ public void getAdSetInsights() throws Exception { assertEquals("video_view", insight.getVideoStartActions().get(0).getActionType()); assertEquals(0, insight.getVideoStartActions().get(0).getValue(), EPSILON); - mockServer.verify(); } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index b7db0391f..3c07bb03e 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -132,7 +132,7 @@ public void getAdCampaignSets() throws Exception { assertEquals("123456789", adSets.get(1).getAccountId()); assertEquals(AdSet.BidType.ABSOLUTE_OCPM, adSets.get(1).getBidType()); assertEquals(0, adSets.get(1).getBudgetRemaining()); - assertEquals("601123456789", adSets.get(1).getCampaignId()); + assertEquals("600123456789", adSets.get(1).getCampaignId()); assertEquals(AdSet.AdSetStatus.ACTIVE, adSets.get(1).getStatus()); assertEquals(toDate("2015-04-10T09:28:54+0200"), adSets.get(1).getCreatedTime()); assertEquals(0, adSets.get(1).getDailyBudget()); @@ -373,4 +373,81 @@ public void deleteAdCampaign() throws Exception { public void deleteAdCampaign_unauthorized() throws Exception { unauthorizedFacebookAds.campaignOperations().deleteAdCampaign("600123456789"); } + + @Test + public void getAdCampaignInsights() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/600123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-campaign-insights"), MediaType.APPLICATION_JSON)); + + AdInsight insight = facebookAds.campaignOperations().getAdCampaignInsight("600123456789"); + assertEquals("123456789", insight.getAccountId()); + assertEquals("Test account name", insight.getAccountName()); + assertEquals(0.016042780748663, insight.getActionsPerImpression(), EPSILON); + assertEquals(8, insight.getClicks()); + assertEquals(5, insight.getUniqueClicks()); + assertEquals(0.66666666666667, insight.getCostPerResult(), EPSILON); + assertEquals(0.66666666666667, insight.getCostPerTotalAction(), EPSILON); + assertEquals(0.25, insight.getCostPerClick(), EPSILON); + assertEquals(0.4, insight.getCostPerUniqueClick(), EPSILON); + assertEquals(10.695187165775, insight.getCpm(), EPSILON); + assertEquals(10.869565217391, insight.getCpp(), EPSILON); + assertEquals(4.2780748663102, insight.getCtr(), EPSILON); + assertEquals(2.7173913043478, insight.getUniqueCtr(), EPSILON); + assertEquals(1.0163043478261, insight.getFrequency(), EPSILON); + assertEquals(187, insight.getImpressions()); + assertEquals(184, insight.getUniqueImpressions()); + assertEquals(184, insight.getReach()); + assertEquals(1.6042780748663, insight.getResultRate(), EPSILON); + assertEquals(3, insight.getResults()); + assertEquals(0, insight.getRoas()); + assertEquals(0, insight.getSocialClicks()); + assertEquals(0, insight.getUniqueSocialClicks()); + assertEquals(0, insight.getSocialImpressions()); + assertEquals(0, insight.getUniqueSocialImpressions()); + assertEquals(0, insight.getSocialReach()); + assertEquals(2, insight.getSpend()); + assertEquals(0, insight.getTodaySpend()); + assertEquals(0, insight.getTotalActionValue()); + assertEquals(3, insight.getTotalActions()); + assertEquals(2, insight.getTotalUniqueActions()); + assertEquals(4, insight.getActions().size()); + assertEquals("comment", insight.getActions().get(0).getActionType()); + assertEquals(2, insight.getActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getActions().get(1).getActionType()); + assertEquals(1, insight.getActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getActions().get(2).getActionType()); + assertEquals(3, insight.getActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getActions().get(3).getActionType()); + assertEquals(3, insight.getActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getUniqueActions().size()); + assertEquals("comment", insight.getUniqueActions().get(0).getActionType()); + assertEquals(1, insight.getUniqueActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getUniqueActions().get(1).getActionType()); + assertEquals(1, insight.getUniqueActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getUniqueActions().get(2).getActionType()); + assertEquals(2, insight.getUniqueActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getUniqueActions().get(3).getActionType()); + assertEquals(2, insight.getUniqueActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getCostPerActionType().size()); + assertEquals("comment", insight.getCostPerActionType().get(0).getActionType()); + assertEquals(1, insight.getCostPerActionType().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getCostPerActionType().get(1).getActionType()); + assertEquals(2, insight.getCostPerActionType().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getCostPerActionType().get(2).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getCostPerActionType().get(3).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(3).getValue(), EPSILON); + assertEquals(1, insight.getVideoStartActions().size()); + assertEquals("video_view", insight.getVideoStartActions().get(0).getActionType()); + assertEquals(0, insight.getVideoStartActions().get(0).getValue(), EPSILON); + + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdCampaignInsights_unauthorized() throws Exception { + unauthorizedFacebookAds.campaignOperations().getAdCampaignInsight("600123456789"); + } } diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-insights.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-insights.json new file mode 100644 index 000000000..2b40792de --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-insights.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "account_id": "123456789", + "account_name": "Test account name", + "date_start": "2015-04-12", + "date_stop": "2015-04-13", + "actions_per_impression": 0.016042780748663, + "clicks": 8, + "unique_clicks": 5, + "cost_per_result": 0.66666666666667, + "cost_per_total_action": 0.66666666666667, + "cpc": 0.25, + "cost_per_unique_click": 0.4, + "cpm": 10.695187165775, + "cpp": 10.869565217391, + "ctr": 4.2780748663102, + "unique_ctr": 2.7173913043478, + "frequency": 1.0163043478261, + "impressions": "187", + "unique_impressions": 184, + "objective": "POST_ENGAGEMENT", + "reach": 184, + "result_rate": 1.6042780748663, + "results": 3, + "roas": 0, + "social_clicks": 0, + "unique_social_clicks": 0, + "social_impressions": 0, + "unique_social_impressions": 0, + "social_reach": 0, + "spend": 2, + "today_spend": 0, + "total_action_value": 0, + "total_actions": 3, + "total_unique_actions": 2, + "actions": [ + { + "action_type": "comment", + "value": 2 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 3 + }, + { + "action_type": "post_engagement", + "value": 3 + } + ], + "unique_actions": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 2 + }, + { + "action_type": "post_engagement", + "value": 2 + } + ], + "cost_per_action_type": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 2 + }, + { + "action_type": "page_engagement", + "value": 0.66666666666667 + }, + { + "action_type": "post_engagement", + "value": 0.66666666666667 + } + ], + "video_start_actions": [ + { + "action_type": "video_view", + "value": 0 + } + ] + } + ], + "paging": { + "cursors": { + "before": "MA==", + "after": "MA==" + } + } +} \ No newline at end of file From 8a5be3d933e8c51a0f82e5219f80719f03bb56d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Mon, 8 Jun 2015 12:56:39 +0200 Subject: [PATCH 20/28] Adds some small changes after testing on real facebook account. --- .../social/facebook/api/ads/AdSetOperations.java | 4 ++-- .../social/facebook/api/ads/impl/AdSetTemplate.java | 2 +- .../social/facebook/api/impl/FacebookTemplate.java | 4 ++-- .../social/facebook/api/ads/AccountTemplateTest.java | 10 +++++----- .../social/facebook/api/ads/AdSetTemplateTest.java | 6 +++--- .../facebook/api/ads/CampaignTemplateTest.java | 12 ++++++------ 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java index 3a60d581e..c72d00894 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSetOperations.java @@ -35,7 +35,7 @@ public interface AdSetOperations { * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. */ - PagedList getAdSets(String accountId); + PagedList getAccountAdSets(String accountId); /** * Get all ad sets for the given campaign @@ -63,7 +63,7 @@ public interface AdSetOperations { * Get the insight for the ad set. * * @param adSetId the id of the ad set - * @return the {@ink Insight} object + * @return the {@link AdInsight} object * @throws ApiException if there is an error while communicating with Facebook. * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java index 2244440f5..58f0d4d49 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java @@ -27,7 +27,7 @@ public AdSetTemplate(GraphApi graphApi, RestTemplate restTemplate, ObjectMapper this.mapper = mapper; } - public PagedList getAdSets(String accountId) { + public PagedList getAccountAdSets(String accountId) { requireAuthorization(); return graphApi.fetchConnections(getAdAccountId(accountId), "adcampaigns", AdSet.class, AdSetOperations.AD_SET_FIELDS); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java index 8bf7eea11..fb44b1d83 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookTemplate.java @@ -278,8 +278,8 @@ public void post(String objectId, String connectionType, MultiValueMap data) { MultiValueMap requestData = new LinkedMultiValueMap(data); URI uri = URIBuilder.fromUri(GRAPH_API_URL + objectId).build(); - Map response = getRestTemplate().postForObject(uri, requestData, Map.class); - return Boolean.valueOf(response.get("success")); + Map response = getRestTemplate().postForObject(uri, requestData, Map.class); + return (Boolean) response.get("success"); } public void delete(String objectId) { diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java index 49099db09..a734a9c88 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java @@ -285,7 +285,7 @@ public void addUserToAccount() throws Exception { .andExpect(method(POST)) .andExpect(content().string(requestBody)) .andExpect(header("Authorization", "OAuth someAccessToken")) - .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\":true}", MediaType.APPLICATION_JSON)); facebookAds.accountOperations().addUserToAdAccount("123456789", "123456", AdUserRole.ADVERTISER); mockServer.verify(); } @@ -302,7 +302,7 @@ public void deleteUserFromAccount() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\":true}", MediaType.APPLICATION_JSON)); facebookAds.accountOperations().deleteUserFromAdAccount("123456789", "123456"); mockServer.verify(); } @@ -394,7 +394,7 @@ public void updateAdAccount_nameOnly() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\":true}", MediaType.APPLICATION_JSON)); AdAccount adAccount = new AdAccount(); adAccount.setName("New Test Name"); boolean updateStatus = facebookAds.accountOperations().updateAdAccount("123456789", adAccount); @@ -409,7 +409,7 @@ public void updateAdAccount_spendCapOnly() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\":true}", MediaType.APPLICATION_JSON)); AdAccount adAccount = new AdAccount(); adAccount.setSpendCap("10000"); boolean updateStatus = facebookAds.accountOperations().updateAdAccount("123456789", adAccount); @@ -424,7 +424,7 @@ public void updateAdAccount_bothNameAndSpendCap() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\":\"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\":true}", MediaType.APPLICATION_JSON)); AdAccount adAccount = new AdAccount(); adAccount.setName("Super cool name"); adAccount.setSpendCap("11111"); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index ea9399a08..b210b77a3 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -30,14 +30,14 @@ public void getAdSets() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andRespond(withSuccess(jsonResource("ad-sets"), MediaType.APPLICATION_JSON)); - PagedList adSets = facebookAds.adSetOperations().getAdSets("123456789"); + PagedList adSets = facebookAds.adSetOperations().getAccountAdSets("123456789"); verifyAdSets(adSets); mockServer.verify(); } @Test(expected = NotAuthorizedException.class) public void getAdSets_unauthorized() throws Exception { - unauthorizedFacebookAds.adSetOperations().getAdSets("123456789"); + unauthorizedFacebookAds.adSetOperations().getAccountAdSets("123456789"); } @Test @@ -325,7 +325,7 @@ public void updateAdSet() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); AdSet adSet = new AdSet(); adSet.setName("New AdSet name"); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index 3c07bb03e..d3099b8a5 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -286,7 +286,7 @@ public void updateAdCampaign_withNameOnly() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); AdCampaign campaign = new AdCampaign(); campaign.setName("New campaign name"); assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); @@ -300,7 +300,7 @@ public void updateAdCampaign_withStatusOnly() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); AdCampaign campaign = new AdCampaign(); campaign.setStatus(CampaignStatus.ACTIVE); assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); @@ -314,7 +314,7 @@ public void updateAdCampaign_withObjectiveOnly() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); AdCampaign campaign = new AdCampaign(); campaign.setObjective(CampaignObjective.POST_ENGAGEMENT); assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); @@ -328,7 +328,7 @@ public void updateAdCampaign_withSpendCapOnly() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); AdCampaign campaign = new AdCampaign(); campaign.setSpendCap(60000); assertTrue(facebookAds.campaignOperations().updateAdCampaign("600123456789", campaign)); @@ -342,7 +342,7 @@ public void updateAdCampaign_withAllFields() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"success\": \"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); AdCampaign campaign = new AdCampaign(); campaign.setName("Updated campaign"); campaign.setStatus(CampaignStatus.ARCHIVED); @@ -364,7 +364,7 @@ public void deleteAdCampaign() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"status\": \"true\"}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"status\": true}", MediaType.APPLICATION_JSON)); facebookAds.campaignOperations().deleteAdCampaign("600123456789"); mockServer.verify(); } From 3d7e4d00d083b4581e91cfacca8b769981689cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Fri, 12 Jun 2015 22:06:27 +0200 Subject: [PATCH 21/28] Adds implementation of AdCreative operations. --- .../social/facebook/api/ads/AdCreative.java | 157 +++++++++ .../facebook/api/ads/CreativeOperations.java | 106 +++++++ .../social/facebook/api/ads/FacebookAds.java | 1 + .../api/ads/impl/CreativeTemplate.java | 112 +++++++ .../api/ads/impl/FacebookAdsTemplate.java | 4 + .../api/ads/impl/json/AdCreativeMixin.java | 56 ++++ .../api/impl/json/FacebookModule.java | 2 + .../api/ads/CampaignTemplateTest.java | 2 +- .../api/ads/CreativeTemplateTest.java | 299 ++++++++++++++++++ .../api/ads/ad-creative-insights.json | 105 ++++++ .../facebook/api/ads/ad-creative-link-ad.json | 12 + .../api/ads/ad-creative-wrong-status.json | 12 + .../api/ads/ad-creative-wrong-type.json | 12 + .../social/facebook/api/ads/ad-creative.json | 8 + .../social/facebook/api/ads/ad-creatives.json | 41 +++ 15 files changed, 928 insertions(+), 1 deletion(-) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java create mode 100644 spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-link-ad.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-status.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-type.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creatives.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java new file mode 100644 index 000000000..13a9c35cb --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java @@ -0,0 +1,157 @@ +package org.springframework.social.facebook.api.ads; + +import com.fasterxml.jackson.annotation.JsonCreator; + +/** + * @author Sebastian Górecki + */ +public class AdCreative { + private String id; + private AdCreativeType type; + private String name; + private String title; + private AdCreativeStatus status; + + private String body; + private String objectId; + private String imageHash; + private String imageUrl; + private String linkUrl; + private String objectStoryId; + private String objectUrl; + private String urlTags; + private String thumbnailUrl; + + public AdCreativeType getType() { + return type; + } + + public void setType(AdCreativeType type) { + this.type = type; + } + + public String getThumbnailUrl() { + return thumbnailUrl; + } + + public void setThumbnailUrl(String thumbnailUrl) { + this.thumbnailUrl = thumbnailUrl; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public AdCreativeStatus getStatus() { + return status; + } + + public void setStatus(AdCreativeStatus status) { + this.status = status; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getObjectId() { + return objectId; + } + + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + public String getImageHash() { + return imageHash; + } + + public void setImageHash(String imageHash) { + this.imageHash = imageHash; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getLinkUrl() { + return linkUrl; + } + + public void setLinkUrl(String linkUrl) { + this.linkUrl = linkUrl; + } + + public String getObjectStoryId() { + return objectStoryId; + } + + public void setObjectStoryId(String objectStoryId) { + this.objectStoryId = objectStoryId; + } + + public String getObjectUrl() { + return objectUrl; + } + + public void setObjectUrl(String objectUrl) { + this.objectUrl = objectUrl; + } + + public String getUrlTags() { + return urlTags; + } + + public void setUrlTags(String urlTags) { + this.urlTags = urlTags; + } + + public enum AdCreativeType { + PAGE, DOMAIN, EVENT, STORE_ITEM, OFFER, SHARE, PHOTO, STATUS, VIDEO, APPLICATION, INVALID, UNKNOWN; + + @JsonCreator + public static AdCreativeType forValue(String value) { + for (AdCreativeType type : AdCreativeType.values()) { + if (type.name().equals(value)) return type; + } + return UNKNOWN; + } + } + + public enum AdCreativeStatus { + PENDING, ACTIVE, PAUSED, DELETED, PENDING_REVIEW, DISAPPROVED, PREAPPROVED, PENDING_BILLING_INFO, + CAMPAIGN_PAUSED, ADGROUP_PAUSED, CAMPAIGN_GROUP_PAUSED, ARCHIVED, UNKNOWN; + + @JsonCreator + public static AdCreativeStatus forValue(String value) { + for (AdCreativeStatus status : AdCreativeStatus.values()) { + if (status.name().equals(value)) return status; + } + return UNKNOWN; + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java new file mode 100644 index 000000000..efd75d593 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java @@ -0,0 +1,106 @@ +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.ApiException; +import org.springframework.social.InsufficientPermissionException; +import org.springframework.social.MissingAuthorizationException; +import org.springframework.social.facebook.api.PagedList; + +/** + * Defines operations for working with Facebook Creative object. + * + * @author Sebastian Górecki + */ +public interface CreativeOperations { + + static final String[] AD_CREATIVE_FIELDS = { + "actor_id", "body", "image_hash", "image_url", "link_url", "name", "object_id", "object_story_id", + "object_url", "run_status", "title", "url_tags", "thumbnail_url", "object_type", "id" + }; + + static final String[] AD_CREATIVE_INSIGHT_FIELDS = { + "account_id", "account_name", "date_start", "date_stop", "actions_per_impression", "clicks", "unique_clicks", + "cost_per_result", "cost_per_total_action", "cpc", "cost_per_unique_click", "cpm", "cpp", "ctr", "unique_ctr", + "frequency", "impressions", "unique_impressions", "objective", "reach", "result_rate", "results", "roas", + "social_clicks", "unique_social_clicks", "social_impressions", "unique_social_impressions", "social_reach", + "spend", "today_spend", "total_action_value", "total_actions", "total_unique_actions", "actions", + "unique_actions", "cost_per_action_type", "video_start_actions" + }; + + /** + * Retrieve an account's ad creatives. + * + * @param accountId the ID of the ad account. + * @return the list of {@link AdCreative} objects. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getAccountCreatives(String accountId); + + /** + * Retrieve an ad set's ad creatives. + * + * @param adSetId the id of the ad set. + * @return the list of {@link AdCreative} objects. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getAdSetCreatives(String adSetId); + + /** + * Get information about an ad creative. + * + * @param creativeId the id of the ad creative + * @return the {@link AdCreative} object. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + AdCreative getAdCreative(String creativeId); + + /** + * Get insights about ad creative. + * + * @param creativeId the id of the ad creative. + * @return the {@link AdInsight} object. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + AdInsight getAdCreativeInsight(String creativeId); + + /** + * Create an ad creative in the given account. + * + * @param accountId the ID of the ad account. + * @param creative the {@link AdInsight} object. + * @return the id of the ad creative + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + String createAdCreative(String accountId, AdCreative creative); + + /** + * Rename an ad creative in creative library. + * + * @param creativeId the id of the ad creative. + * @param name new name of the ad creative + * @return true if rename was successful + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + boolean renameAdCreative(String creativeId, String name); + + /** + * Delete given ad creative. + * + * @param creativeId the id of the ad creative + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void deleteAdCreative(String creativeId); +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java index 7383901a2..30848a6f0 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java @@ -12,4 +12,5 @@ public interface FacebookAds { AccountOperations accountOperations(); CampaignOperations campaignOperations(); AdSetOperations adSetOperations(); + CreativeOperations creativeOperations(); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java new file mode 100644 index 000000000..c91583b24 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java @@ -0,0 +1,112 @@ +package org.springframework.social.facebook.api.ads.impl; + +import org.springframework.social.facebook.api.GraphApi; +import org.springframework.social.facebook.api.PagedList; +import org.springframework.social.facebook.api.ads.AdCreative; +import org.springframework.social.facebook.api.ads.AdInsight; +import org.springframework.social.facebook.api.ads.CreativeOperations; +import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +/** + * @author Sebastian Górecki + */ +public class CreativeTemplate extends AbstractFacebookOperations implements CreativeOperations { + + private final GraphApi graphApi; + private final RestTemplate restTemplate; + + + public CreativeTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean isAuthorizedForUser) { + super(isAuthorizedForUser); + this.graphApi = graphApi; + this.restTemplate = restTemplate; + } + + public PagedList getAccountCreatives(String accountId) { + requireAuthorization(); + return graphApi.fetchConnections(getAdAccountId(accountId), "adcreatives", AdCreative.class, CreativeOperations.AD_CREATIVE_FIELDS); + } + + public PagedList getAdSetCreatives(String adSetId) { + requireAuthorization(); + return graphApi.fetchConnections(adSetId, "adcreatives", AdCreative.class, CreativeOperations.AD_CREATIVE_FIELDS); + } + + public AdCreative getAdCreative(String creativeId) { + requireAuthorization(); + return graphApi.fetchObject(creativeId, AdCreative.class, CreativeOperations.AD_CREATIVE_FIELDS); + } + + public AdInsight getAdCreativeInsight(String creativeId) { + requireAuthorization(); + PagedList insights = graphApi.fetchConnections(creativeId, "insights", AdInsight.class, CreativeOperations.AD_CREATIVE_INSIGHT_FIELDS); + return insights.get(0); + } + + public String createAdCreative(String accountId, AdCreative creative) { + requireAuthorization(); + MultiValueMap data = mapCommonFields(creative); + return graphApi.publish(getAdAccountId(accountId), "adcreatives", data); + } + + public boolean renameAdCreative(String creativeId, String name) { + requireAuthorization(); + MultiValueMap data = new LinkedMultiValueMap(); + data.add("name", name); + return graphApi.update(creativeId, data); + } + + public void deleteAdCreative(String creativeId) { + requireAuthorization(); + MultiValueMap data = new LinkedMultiValueMap(); + data.add("run_status", AdCreative.AdCreativeStatus.DELETED.name()); + graphApi.post(creativeId, data); + } + + private MultiValueMap mapCommonFields(AdCreative creative) { + MultiValueMap data = new LinkedMultiValueMap(); + if (creative.getType() != null) { + data.add("object_type", creative.getType().name()); + } + if (creative.getName() != null) { + data.add("name", creative.getName()); + } + if (creative.getTitle() != null) { + data.add("title", creative.getTitle()); + } + if (creative.getStatus() != null) { + data.add("run_status", creative.getStatus().name()); + } + if (creative.getBody() != null) { + data.add("body", creative.getBody()); + } + if (creative.getObjectId() != null) { + data.add("object_id", creative.getObjectId()); + } + if (creative.getImageHash() != null) { + data.add("image_hash", creative.getImageHash()); + } + if (creative.getImageUrl() != null) { + data.add("image_url", creative.getImageUrl()); + } + if (creative.getLinkUrl() != null) { + data.add("link_url", creative.getLinkUrl()); + } + if (creative.getObjectStoryId() != null) { + data.add("object_story_id", creative.getObjectStoryId()); + } + if (creative.getObjectUrl() != null) { + data.add("object_url", creative.getObjectUrl()); + } + if (creative.getUrlTags() != null) { + data.add("url_tags", creative.getUrlTags()); + } + if (creative.getThumbnailUrl() != null) { + data.add("thumbnail_url", creative.getThumbnailUrl()); + } + return data; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java index e8a50144e..3c4079c07 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java @@ -15,6 +15,7 @@ public class FacebookAdsTemplate extends FacebookTemplate implements FacebookAds private AccountOperations accountOperations; private CampaignOperations campaignOperations; private AdSetOperations adSetOperations; + private CreativeOperations creativeOperations; public FacebookAdsTemplate() { super(null); @@ -36,9 +37,12 @@ public CampaignOperations campaignOperations() { public AdSetOperations adSetOperations() { return adSetOperations; } + public CreativeOperations creativeOperations() { return creativeOperations; } + private void initSubApis() { accountOperations = new AccountTemplate(this, getRestTemplate(), isAuthorized()); campaignOperations = new CampaignTemplate(this, getRestTemplate(), isAuthorized()); adSetOperations = new AdSetTemplate(this, getRestTemplate(), getJsonMessageConverter().getObjectMapper(), isAuthorized()); + creativeOperations = new CreativeTemplate(this, getRestTemplate(), isAuthorized()); } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java new file mode 100644 index 000000000..04ec0ecd1 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java @@ -0,0 +1,56 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.social.facebook.api.ads.AdCreative; +import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; + +/** + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +abstract public class AdCreativeMixin extends FacebookObjectMixin { + + @JsonProperty("id") + private String id; + + @JsonProperty("object_type") + private AdCreative.AdCreativeType type; + + @JsonProperty("name") + private String name; + + @JsonProperty("title") + private String title; + + @JsonProperty("run_status") + private AdCreative.AdCreativeStatus status; + + @JsonProperty("body") + private String body; + + @JsonProperty("object_id") + private String objectId; + + @JsonProperty("image_hash") + private String imageHash; + + @JsonProperty("image_url") + private String imageUrl; + + @JsonProperty("link_url") + private String linkUrl; + + @JsonProperty("object_story_id") + private String objectStoryId; + + @JsonProperty("object_url") + private String objectUrl; + + @JsonProperty("url_tags") + private String urlTags; + + @JsonProperty("thumbnail_url") + private String thumbnailUrl; + +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java index 4d5bb93ab..dc61e805b 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java @@ -139,5 +139,7 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(TargetingEntry.class, TargetingEntryMixin.class); context.setMixInAnnotations(TargetingLocation.class, TargetingLocationMixin.class); context.setMixInAnnotations(AdSet.class, AdSetMixin.class); + + context.setMixInAnnotations(AdCreative.class, AdCreativeMixin.class); } } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index d3099b8a5..812ce38bd 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -364,7 +364,7 @@ public void deleteAdCampaign() throws Exception { .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) - .andRespond(withSuccess("{\"status\": true}", MediaType.APPLICATION_JSON)); + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); facebookAds.campaignOperations().deleteAdCampaign("600123456789"); mockServer.verify(); } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java new file mode 100644 index 000000000..6167bf5f8 --- /dev/null +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java @@ -0,0 +1,299 @@ +package org.springframework.social.facebook.api.ads; + +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.social.NotAuthorizedException; +import org.springframework.social.facebook.api.PagedList; + +import java.util.List; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.springframework.http.HttpMethod.DELETE; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * @author Sebastian Górecki + */ +public class CreativeTemplateTest extends AbstractFacebookAdsApiTest { + + @Test + public void getAccountCreatives() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcreatives?fields=actor_id%2Cbody%2Cimage_hash%2Cimage_url%2Clink_url%2Cname%2Cobject_id%2Cobject_story_id%2Cobject_url%2Crun_status%2Ctitle%2Curl_tags%2Cthumbnail_url%2Cobject_type%2Cid")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-creatives"), MediaType.APPLICATION_JSON)); + + List creatives = facebookAds.creativeOperations().getAccountCreatives("123456789"); + verifyAdCreatives(creatives); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAccountCreatives_unauthorized() throws Exception { + unauthorizedFacebookAds.creativeOperations().getAccountCreatives("123456789"); + } + + @Test + public void getAdSetCreatives() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/700123456789/adcreatives?fields=actor_id%2Cbody%2Cimage_hash%2Cimage_url%2Clink_url%2Cname%2Cobject_id%2Cobject_story_id%2Cobject_url%2Crun_status%2Ctitle%2Curl_tags%2Cthumbnail_url%2Cobject_type%2Cid")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-creatives"), MediaType.APPLICATION_JSON)); + + List creatives = facebookAds.creativeOperations().getAdSetCreatives("700123456789"); + verifyAdCreatives(creatives); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdSetCreatives_unauthorized() throws Exception { + unauthorizedFacebookAds.creativeOperations().getAdSetCreatives("700123456789"); + } + + @Test + public void getAdCreative() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/800123456789?fields=actor_id%2Cbody%2Cimage_hash%2Cimage_url%2Clink_url%2Cname%2Cobject_id%2Cobject_story_id%2Cobject_url%2Crun_status%2Ctitle%2Curl_tags%2Cthumbnail_url%2Cobject_type%2Cid")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-creative"), MediaType.APPLICATION_JSON)); + + AdCreative creative = facebookAds.creativeOperations().getAdCreative("800123456789"); + assertEquals("800123456789", creative.getId()); + assertEquals(AdCreative.AdCreativeType.STATUS, creative.getType()); + assertEquals("https://example.net/safe_image.php?d=123456789", creative.getThumbnailUrl()); + assertEquals(AdCreative.AdCreativeStatus.ACTIVE, creative.getStatus()); + assertEquals("123456789_987654321", creative.getObjectStoryId()); + assertEquals("Creative test name", creative.getName()); + mockServer.verify(); + } + + @Test + public void getAdCreative_withWrongType() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/801123456789?fields=actor_id%2Cbody%2Cimage_hash%2Cimage_url%2Clink_url%2Cname%2Cobject_id%2Cobject_story_id%2Cobject_url%2Crun_status%2Ctitle%2Curl_tags%2Cthumbnail_url%2Cobject_type%2Cid")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-creative-wrong-type"), MediaType.APPLICATION_JSON)); + AdCreative creative = facebookAds.creativeOperations().getAdCreative("801123456789"); + assertEquals("801123456789", creative.getId()); + assertEquals(AdCreative.AdCreativeType.UNKNOWN, creative.getType()); + verifyAdCreative(creative); + assertEquals(AdCreative.AdCreativeStatus.ACTIVE, creative.getStatus()); + mockServer.verify(); + } + + @Test + public void getAdCreative_withWrongStatus() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/802123456789?fields=actor_id%2Cbody%2Cimage_hash%2Cimage_url%2Clink_url%2Cname%2Cobject_id%2Cobject_story_id%2Cobject_url%2Crun_status%2Ctitle%2Curl_tags%2Cthumbnail_url%2Cobject_type%2Cid")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-creative-wrong-status"), MediaType.APPLICATION_JSON)); + AdCreative creative = facebookAds.creativeOperations().getAdCreative("802123456789"); + assertEquals("802123456789", creative.getId()); + assertEquals(AdCreative.AdCreativeType.DOMAIN, creative.getType()); + verifyAdCreative(creative); + assertEquals(AdCreative.AdCreativeStatus.UNKNOWN, creative.getStatus()); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdCreative_unauthorized() throws Exception { + unauthorizedFacebookAds.creativeOperations().getAdCreative("800123456789"); + } + + @Test + public void getAdCreativeInsight() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/800123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-creative-insights"), MediaType.APPLICATION_JSON)); + + AdInsight insight = facebookAds.creativeOperations().getAdCreativeInsight("800123456789"); + assertEquals("123456789", insight.getAccountId()); + assertEquals("Test account name", insight.getAccountName()); + assertEquals(0.016042780748663, insight.getActionsPerImpression(), EPSILON); + assertEquals(8, insight.getClicks()); + assertEquals(5, insight.getUniqueClicks()); + assertEquals(0.66666666666667, insight.getCostPerResult(), EPSILON); + assertEquals(0.66666666666667, insight.getCostPerTotalAction(), EPSILON); + assertEquals(0.25, insight.getCostPerClick(), EPSILON); + assertEquals(0.4, insight.getCostPerUniqueClick(), EPSILON); + assertEquals(10.695187165775, insight.getCpm(), EPSILON); + assertEquals(10.869565217391, insight.getCpp(), EPSILON); + assertEquals(4.2780748663102, insight.getCtr(), EPSILON); + assertEquals(2.7173913043478, insight.getUniqueCtr(), EPSILON); + assertEquals(1.0163043478261, insight.getFrequency(), EPSILON); + assertEquals(187, insight.getImpressions()); + assertEquals(184, insight.getUniqueImpressions()); + assertEquals(184, insight.getReach()); + assertEquals(1.6042780748663, insight.getResultRate(), EPSILON); + assertEquals(3, insight.getResults()); + assertEquals(0, insight.getRoas()); + assertEquals(0, insight.getSocialClicks()); + assertEquals(0, insight.getUniqueSocialClicks()); + assertEquals(0, insight.getSocialImpressions()); + assertEquals(0, insight.getUniqueSocialImpressions()); + assertEquals(0, insight.getSocialReach()); + assertEquals(2, insight.getSpend()); + assertEquals(0, insight.getTodaySpend()); + assertEquals(0, insight.getTotalActionValue()); + assertEquals(3, insight.getTotalActions()); + assertEquals(2, insight.getTotalUniqueActions()); + assertEquals(4, insight.getActions().size()); + assertEquals("comment", insight.getActions().get(0).getActionType()); + assertEquals(2, insight.getActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getActions().get(1).getActionType()); + assertEquals(1, insight.getActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getActions().get(2).getActionType()); + assertEquals(3, insight.getActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getActions().get(3).getActionType()); + assertEquals(3, insight.getActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getUniqueActions().size()); + assertEquals("comment", insight.getUniqueActions().get(0).getActionType()); + assertEquals(1, insight.getUniqueActions().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getUniqueActions().get(1).getActionType()); + assertEquals(1, insight.getUniqueActions().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getUniqueActions().get(2).getActionType()); + assertEquals(2, insight.getUniqueActions().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getUniqueActions().get(3).getActionType()); + assertEquals(2, insight.getUniqueActions().get(3).getValue(), EPSILON); + assertEquals(4, insight.getCostPerActionType().size()); + assertEquals("comment", insight.getCostPerActionType().get(0).getActionType()); + assertEquals(1, insight.getCostPerActionType().get(0).getValue(), EPSILON); + assertEquals("post_like", insight.getCostPerActionType().get(1).getActionType()); + assertEquals(2, insight.getCostPerActionType().get(1).getValue(), EPSILON); + assertEquals("page_engagement", insight.getCostPerActionType().get(2).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(2).getValue(), EPSILON); + assertEquals("post_engagement", insight.getCostPerActionType().get(3).getActionType()); + assertEquals(0.66666666666667, insight.getCostPerActionType().get(3).getValue(), EPSILON); + assertEquals(1, insight.getVideoStartActions().size()); + assertEquals("video_view", insight.getVideoStartActions().get(0).getActionType()); + assertEquals(0, insight.getVideoStartActions().get(0).getValue(), EPSILON); + + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdCreativeInsight_unauthorized() throws Exception { + unauthorizedFacebookAds.creativeOperations().getAdCreativeInsight("800123456789"); + } + + @Test + public void createAdCreative_linkAd() throws Exception { + String requestBody = "title=Ad+creative+title&body=Over+the+past+few+years+REST+has+become+an+important&image_url=http%3A%2F%2Fexample.com%2Fstatics_s2_20150603-0041%2Fstyles%2Fi%2Flogo_bigger.jpg&object_url=http%3A%2F%2Fwww.example.com%2Fsome_object"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcreatives")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"801123456789\"}", MediaType.APPLICATION_JSON)); + + AdCreative creative = new AdCreative(); + creative.setTitle("Ad creative title"); + creative.setObjectUrl("http://www.example.com/some_object"); + creative.setBody("Over the past few years REST has become an important"); + creative.setImageUrl("http://example.com/statics_s2_20150603-0041/styles/i/logo_bigger.jpg"); + assertEquals("801123456789", facebookAds.creativeOperations().createAdCreative("123456789", creative)); + mockServer.verify(); + } + + @Test + public void createAdCreative_postLikeAd() throws Exception { + String requestBody = "name=Post+Like+Ad+Creative&body=Some+body+here&object_id=600123456789"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcreatives")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"802123456789\"}", MediaType.APPLICATION_JSON)); + + AdCreative creative = new AdCreative(); + creative.setName("Post Like Ad Creative"); + creative.setObjectId("600123456789"); + creative.setBody("Some body here"); + assertEquals("802123456789", facebookAds.creativeOperations().createAdCreative("123456789", creative)); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void createAdCreative_unauthorized() throws Exception { + unauthorizedFacebookAds.creativeOperations().createAdCreative("123456789", new AdCreative()); + } + + @Test + public void renameAdCreative() throws Exception { + String requestBody = "name=New+creative+name"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/800123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); + + facebookAds.creativeOperations().renameAdCreative("800123456789", "New creative name"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void renameAdCreative_unauthorized() throws Exception { + unauthorizedFacebookAds.creativeOperations().renameAdCreative("800123456789", "New anauthorized name!"); + } + + @Test + public void deleteAdCreative() throws Exception { + String requestBody = "run_status=DELETED"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/800123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); + + facebookAds.creativeOperations().deleteAdCreative("800123456789"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void deleteAdCreative_unauthorized() throws Exception { + unauthorizedFacebookAds.creativeOperations().deleteAdCreative("800123456789"); + } + + private void verifyAdCreatives(List creatives) { + assertEquals(3, creatives.size()); + assertEquals("This is the body of the ad creative1", creatives.get(0).getBody()); + assertEquals("ac67a06abb35a3bef8256c8c9085548e1", creatives.get(0).getImageHash()); + assertEquals("https://example.com/images/image1.png", creatives.get(0).getImageUrl()); + assertEquals("Name of the ad creative1", creatives.get(0).getName()); + assertEquals("900123456789", creatives.get(0).getObjectId()); + assertEquals(AdCreative.AdCreativeStatus.ACTIVE, creatives.get(0).getStatus()); + assertEquals("https://example.com/thumbnails/thuumbnail_url1", creatives.get(0).getThumbnailUrl()); + assertEquals(AdCreative.AdCreativeType.PAGE, creatives.get(0).getType()); + assertEquals("801123456789", creatives.get(0).getId()); + assertEquals("This is the body of the ad creative2", creatives.get(1).getBody()); + assertEquals("ac67a06abb35a3bef8256c8c9085548e2", creatives.get(1).getImageHash()); + assertEquals("https://example.com/images/image2.png", creatives.get(1).getImageUrl()); + assertEquals("Name of the ad creative2", creatives.get(1).getName()); + assertEquals("http://example.com/objects/object", creatives.get(1).getObjectUrl()); + assertEquals(AdCreative.AdCreativeStatus.ACTIVE, creatives.get(1).getStatus()); + assertEquals("Test Ad Creative", creatives.get(1).getTitle()); + assertEquals("https://example.com/thumbnails/thuumbnail_url2", creatives.get(1).getThumbnailUrl()); + assertEquals(AdCreative.AdCreativeType.DOMAIN, creatives.get(1).getType()); + assertEquals("802123456789", creatives.get(1).getId()); + assertEquals("This is the body of the ad creative3", creatives.get(2).getName()); + assertEquals("987654321_123456789", creatives.get(2).getObjectStoryId()); + assertEquals(AdCreative.AdCreativeStatus.ACTIVE, creatives.get(2).getStatus()); + assertEquals("https://example.com/thumbnails/thuumbnail_url3", creatives.get(2).getThumbnailUrl()); + assertEquals(AdCreative.AdCreativeType.STATUS, creatives.get(2).getType()); + assertEquals("803123456789", creatives.get(2).getId()); + } + + private void verifyAdCreative(AdCreative creative) { + assertEquals("This is the body of the ad creative", creative.getBody()); + assertEquals("ac67a06abb35a3bef8256c8c9085548e", creative.getImageHash()); + assertEquals("https://example.com/images/image.png", creative.getImageUrl()); + assertEquals("Name of the ad creative", creative.getName()); + assertEquals("http://example.com/objects/object", creative.getObjectUrl()); + assertEquals("Title of the ad creative", creative.getTitle()); + assertEquals("https://example.com/thumbnails/thuumbnail_url", creative.getThumbnailUrl()); + } +} diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json new file mode 100644 index 000000000..2b40792de --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "account_id": "123456789", + "account_name": "Test account name", + "date_start": "2015-04-12", + "date_stop": "2015-04-13", + "actions_per_impression": 0.016042780748663, + "clicks": 8, + "unique_clicks": 5, + "cost_per_result": 0.66666666666667, + "cost_per_total_action": 0.66666666666667, + "cpc": 0.25, + "cost_per_unique_click": 0.4, + "cpm": 10.695187165775, + "cpp": 10.869565217391, + "ctr": 4.2780748663102, + "unique_ctr": 2.7173913043478, + "frequency": 1.0163043478261, + "impressions": "187", + "unique_impressions": 184, + "objective": "POST_ENGAGEMENT", + "reach": 184, + "result_rate": 1.6042780748663, + "results": 3, + "roas": 0, + "social_clicks": 0, + "unique_social_clicks": 0, + "social_impressions": 0, + "unique_social_impressions": 0, + "social_reach": 0, + "spend": 2, + "today_spend": 0, + "total_action_value": 0, + "total_actions": 3, + "total_unique_actions": 2, + "actions": [ + { + "action_type": "comment", + "value": 2 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 3 + }, + { + "action_type": "post_engagement", + "value": 3 + } + ], + "unique_actions": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 2 + }, + { + "action_type": "post_engagement", + "value": 2 + } + ], + "cost_per_action_type": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 2 + }, + { + "action_type": "page_engagement", + "value": 0.66666666666667 + }, + { + "action_type": "post_engagement", + "value": 0.66666666666667 + } + ], + "video_start_actions": [ + { + "action_type": "video_view", + "value": 0 + } + ] + } + ], + "paging": { + "cursors": { + "before": "MA==", + "after": "MA==" + } + } +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-link-ad.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-link-ad.json new file mode 100644 index 000000000..c722b4179 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-link-ad.json @@ -0,0 +1,12 @@ +{ + "body": "This is the body of the ad creative", + "image_hash": "ac67a06abb35a3bef8256c8c9085548e", + "image_url": "https://example.com/images/image.png", + "name": "Name of the ad creative", + "object_url": "http://example.com/objects/object", + "run_status": "ACTIVE", + "title": "Title of the ad creative", + "thumbnail_url": "https://example.com/thumbnails/thuumbnail_url", + "object_type": "DOMAIN", + "id": "803123456789" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-status.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-status.json new file mode 100644 index 000000000..2aeec90b0 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-status.json @@ -0,0 +1,12 @@ +{ + "body": "This is the body of the ad creative", + "image_hash": "ac67a06abb35a3bef8256c8c9085548e", + "image_url": "https://example.com/images/image.png", + "name": "Name of the ad creative", + "object_url": "http://example.com/objects/object", + "run_status": "WRONG_STATUS", + "title": "Title of the ad creative", + "thumbnail_url": "https://example.com/thumbnails/thuumbnail_url", + "object_type": "DOMAIN", + "id": "802123456789" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-type.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-type.json new file mode 100644 index 000000000..f4051aabb --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-wrong-type.json @@ -0,0 +1,12 @@ +{ + "body": "This is the body of the ad creative", + "image_hash": "ac67a06abb35a3bef8256c8c9085548e", + "image_url": "https://example.com/images/image.png", + "name": "Name of the ad creative", + "object_url": "http://example.com/objects/object", + "run_status": "ACTIVE", + "title": "Title of the ad creative", + "thumbnail_url": "https://example.com/thumbnails/thuumbnail_url", + "object_type": "WRONG_TYPE", + "id": "801123456789" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative.json new file mode 100644 index 000000000..bd207d7a0 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative.json @@ -0,0 +1,8 @@ +{ + "name": "Creative test name", + "object_story_id": "123456789_987654321", + "run_status": "ACTIVE", + "thumbnail_url": "https://example.net/safe_image.php?d=123456789", + "object_type": "STATUS", + "id": "800123456789" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creatives.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creatives.json new file mode 100644 index 000000000..1fd77f820 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creatives.json @@ -0,0 +1,41 @@ +{ + "data": [ + { + "body": "This is the body of the ad creative1", + "image_hash": "ac67a06abb35a3bef8256c8c9085548e1", + "image_url": "https://example.com/images/image1.png", + "name": "Name of the ad creative1", + "object_id": "900123456789", + "run_status": "ACTIVE", + "thumbnail_url": "https://example.com/thumbnails/thuumbnail_url1", + "object_type": "PAGE", + "id": "801123456789" + }, + { + "body": "This is the body of the ad creative2", + "image_hash": "ac67a06abb35a3bef8256c8c9085548e2", + "image_url": "https://example.com/images/image2.png", + "name": "Name of the ad creative2", + "object_url": "http://example.com/objects/object", + "run_status": "ACTIVE", + "title": "Test Ad Creative", + "thumbnail_url": "https://example.com/thumbnails/thuumbnail_url2", + "object_type": "DOMAIN", + "id": "802123456789" + }, + { + "name": "This is the body of the ad creative3", + "object_story_id": "987654321_123456789", + "run_status": "ACTIVE", + "thumbnail_url": "https://example.com/thumbnails/thuumbnail_url3", + "object_type": "STATUS", + "id": "803123456789" + } + ], + "paging": { + "cursors": { + "before": "NjAyNjU3MzQzNDQ1MA==", + "after": "NjAyNDE2Nzg5NjY1MA==" + } + } +} \ No newline at end of file From ed756bef90a9b4e55fc519d529aabae018bb982e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Mon, 15 Jun 2015 12:00:38 +0200 Subject: [PATCH 22/28] Remove insight operations on AdCreative. Change delete operation to HTTP DELETE. --- .../facebook/api/ads/CreativeOperations.java | 20 ---- .../api/ads/impl/CreativeTemplate.java | 10 +- .../api/ads/CreativeTemplateTest.java | 81 +------------- .../api/ads/ad-creative-insights.json | 105 ------------------ 4 files changed, 2 insertions(+), 214 deletions(-) delete mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java index efd75d593..753045f96 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CreativeOperations.java @@ -17,15 +17,6 @@ public interface CreativeOperations { "object_url", "run_status", "title", "url_tags", "thumbnail_url", "object_type", "id" }; - static final String[] AD_CREATIVE_INSIGHT_FIELDS = { - "account_id", "account_name", "date_start", "date_stop", "actions_per_impression", "clicks", "unique_clicks", - "cost_per_result", "cost_per_total_action", "cpc", "cost_per_unique_click", "cpm", "cpp", "ctr", "unique_ctr", - "frequency", "impressions", "unique_impressions", "objective", "reach", "result_rate", "results", "roas", - "social_clicks", "unique_social_clicks", "social_impressions", "unique_social_impressions", "social_reach", - "spend", "today_spend", "total_action_value", "total_actions", "total_unique_actions", "actions", - "unique_actions", "cost_per_action_type", "video_start_actions" - }; - /** * Retrieve an account's ad creatives. * @@ -59,17 +50,6 @@ public interface CreativeOperations { */ AdCreative getAdCreative(String creativeId); - /** - * Get insights about ad creative. - * - * @param creativeId the id of the ad creative. - * @return the {@link AdInsight} object. - * @throws ApiException if there is an error while communicating with Facebook. - * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. - * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. - */ - AdInsight getAdCreativeInsight(String creativeId); - /** * Create an ad creative in the given account. * diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java index c91583b24..51f06a066 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CreativeTemplate.java @@ -40,12 +40,6 @@ public AdCreative getAdCreative(String creativeId) { return graphApi.fetchObject(creativeId, AdCreative.class, CreativeOperations.AD_CREATIVE_FIELDS); } - public AdInsight getAdCreativeInsight(String creativeId) { - requireAuthorization(); - PagedList insights = graphApi.fetchConnections(creativeId, "insights", AdInsight.class, CreativeOperations.AD_CREATIVE_INSIGHT_FIELDS); - return insights.get(0); - } - public String createAdCreative(String accountId, AdCreative creative) { requireAuthorization(); MultiValueMap data = mapCommonFields(creative); @@ -61,9 +55,7 @@ public boolean renameAdCreative(String creativeId, String name) { public void deleteAdCreative(String creativeId) { requireAuthorization(); - MultiValueMap data = new LinkedMultiValueMap(); - data.add("run_status", AdCreative.AdCreativeStatus.DELETED.name()); - graphApi.post(creativeId, data); + restTemplate.delete(GraphApi.GRAPH_API_URL + creativeId); } private MultiValueMap mapCommonFields(AdCreative creative) { diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java index 6167bf5f8..76743a57c 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CreativeTemplateTest.java @@ -105,83 +105,6 @@ public void getAdCreative_unauthorized() throws Exception { unauthorizedFacebookAds.creativeOperations().getAdCreative("800123456789"); } - @Test - public void getAdCreativeInsight() throws Exception { - mockServer.expect(requestTo("https://graph.facebook.com/v2.3/800123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions")) - .andExpect(method(GET)) - .andExpect(header("Authorization", "OAuth someAccessToken")) - .andRespond(withSuccess(jsonResource("ad-creative-insights"), MediaType.APPLICATION_JSON)); - - AdInsight insight = facebookAds.creativeOperations().getAdCreativeInsight("800123456789"); - assertEquals("123456789", insight.getAccountId()); - assertEquals("Test account name", insight.getAccountName()); - assertEquals(0.016042780748663, insight.getActionsPerImpression(), EPSILON); - assertEquals(8, insight.getClicks()); - assertEquals(5, insight.getUniqueClicks()); - assertEquals(0.66666666666667, insight.getCostPerResult(), EPSILON); - assertEquals(0.66666666666667, insight.getCostPerTotalAction(), EPSILON); - assertEquals(0.25, insight.getCostPerClick(), EPSILON); - assertEquals(0.4, insight.getCostPerUniqueClick(), EPSILON); - assertEquals(10.695187165775, insight.getCpm(), EPSILON); - assertEquals(10.869565217391, insight.getCpp(), EPSILON); - assertEquals(4.2780748663102, insight.getCtr(), EPSILON); - assertEquals(2.7173913043478, insight.getUniqueCtr(), EPSILON); - assertEquals(1.0163043478261, insight.getFrequency(), EPSILON); - assertEquals(187, insight.getImpressions()); - assertEquals(184, insight.getUniqueImpressions()); - assertEquals(184, insight.getReach()); - assertEquals(1.6042780748663, insight.getResultRate(), EPSILON); - assertEquals(3, insight.getResults()); - assertEquals(0, insight.getRoas()); - assertEquals(0, insight.getSocialClicks()); - assertEquals(0, insight.getUniqueSocialClicks()); - assertEquals(0, insight.getSocialImpressions()); - assertEquals(0, insight.getUniqueSocialImpressions()); - assertEquals(0, insight.getSocialReach()); - assertEquals(2, insight.getSpend()); - assertEquals(0, insight.getTodaySpend()); - assertEquals(0, insight.getTotalActionValue()); - assertEquals(3, insight.getTotalActions()); - assertEquals(2, insight.getTotalUniqueActions()); - assertEquals(4, insight.getActions().size()); - assertEquals("comment", insight.getActions().get(0).getActionType()); - assertEquals(2, insight.getActions().get(0).getValue(), EPSILON); - assertEquals("post_like", insight.getActions().get(1).getActionType()); - assertEquals(1, insight.getActions().get(1).getValue(), EPSILON); - assertEquals("page_engagement", insight.getActions().get(2).getActionType()); - assertEquals(3, insight.getActions().get(2).getValue(), EPSILON); - assertEquals("post_engagement", insight.getActions().get(3).getActionType()); - assertEquals(3, insight.getActions().get(3).getValue(), EPSILON); - assertEquals(4, insight.getUniqueActions().size()); - assertEquals("comment", insight.getUniqueActions().get(0).getActionType()); - assertEquals(1, insight.getUniqueActions().get(0).getValue(), EPSILON); - assertEquals("post_like", insight.getUniqueActions().get(1).getActionType()); - assertEquals(1, insight.getUniqueActions().get(1).getValue(), EPSILON); - assertEquals("page_engagement", insight.getUniqueActions().get(2).getActionType()); - assertEquals(2, insight.getUniqueActions().get(2).getValue(), EPSILON); - assertEquals("post_engagement", insight.getUniqueActions().get(3).getActionType()); - assertEquals(2, insight.getUniqueActions().get(3).getValue(), EPSILON); - assertEquals(4, insight.getCostPerActionType().size()); - assertEquals("comment", insight.getCostPerActionType().get(0).getActionType()); - assertEquals(1, insight.getCostPerActionType().get(0).getValue(), EPSILON); - assertEquals("post_like", insight.getCostPerActionType().get(1).getActionType()); - assertEquals(2, insight.getCostPerActionType().get(1).getValue(), EPSILON); - assertEquals("page_engagement", insight.getCostPerActionType().get(2).getActionType()); - assertEquals(0.66666666666667, insight.getCostPerActionType().get(2).getValue(), EPSILON); - assertEquals("post_engagement", insight.getCostPerActionType().get(3).getActionType()); - assertEquals(0.66666666666667, insight.getCostPerActionType().get(3).getValue(), EPSILON); - assertEquals(1, insight.getVideoStartActions().size()); - assertEquals("video_view", insight.getVideoStartActions().get(0).getActionType()); - assertEquals(0, insight.getVideoStartActions().get(0).getValue(), EPSILON); - - mockServer.verify(); - } - - @Test(expected = NotAuthorizedException.class) - public void getAdCreativeInsight_unauthorized() throws Exception { - unauthorizedFacebookAds.creativeOperations().getAdCreativeInsight("800123456789"); - } - @Test public void createAdCreative_linkAd() throws Exception { String requestBody = "title=Ad+creative+title&body=Over+the+past+few+years+REST+has+become+an+important&image_url=http%3A%2F%2Fexample.com%2Fstatics_s2_20150603-0041%2Fstyles%2Fi%2Flogo_bigger.jpg&object_url=http%3A%2F%2Fwww.example.com%2Fsome_object"; @@ -242,11 +165,9 @@ public void renameAdCreative_unauthorized() throws Exception { @Test public void deleteAdCreative() throws Exception { - String requestBody = "run_status=DELETED"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/800123456789")) - .andExpect(method(POST)) + .andExpect(method(DELETE)) .andExpect(header("Authorization", "OAuth someAccessToken")) - .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); facebookAds.creativeOperations().deleteAdCreative("800123456789"); diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json deleted file mode 100644 index 2b40792de..000000000 --- a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-creative-insights.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "data": [ - { - "account_id": "123456789", - "account_name": "Test account name", - "date_start": "2015-04-12", - "date_stop": "2015-04-13", - "actions_per_impression": 0.016042780748663, - "clicks": 8, - "unique_clicks": 5, - "cost_per_result": 0.66666666666667, - "cost_per_total_action": 0.66666666666667, - "cpc": 0.25, - "cost_per_unique_click": 0.4, - "cpm": 10.695187165775, - "cpp": 10.869565217391, - "ctr": 4.2780748663102, - "unique_ctr": 2.7173913043478, - "frequency": 1.0163043478261, - "impressions": "187", - "unique_impressions": 184, - "objective": "POST_ENGAGEMENT", - "reach": 184, - "result_rate": 1.6042780748663, - "results": 3, - "roas": 0, - "social_clicks": 0, - "unique_social_clicks": 0, - "social_impressions": 0, - "unique_social_impressions": 0, - "social_reach": 0, - "spend": 2, - "today_spend": 0, - "total_action_value": 0, - "total_actions": 3, - "total_unique_actions": 2, - "actions": [ - { - "action_type": "comment", - "value": 2 - }, - { - "action_type": "post_like", - "value": 1 - }, - { - "action_type": "page_engagement", - "value": 3 - }, - { - "action_type": "post_engagement", - "value": 3 - } - ], - "unique_actions": [ - { - "action_type": "comment", - "value": 1 - }, - { - "action_type": "post_like", - "value": 1 - }, - { - "action_type": "page_engagement", - "value": 2 - }, - { - "action_type": "post_engagement", - "value": 2 - } - ], - "cost_per_action_type": [ - { - "action_type": "comment", - "value": 1 - }, - { - "action_type": "post_like", - "value": 2 - }, - { - "action_type": "page_engagement", - "value": 0.66666666666667 - }, - { - "action_type": "post_engagement", - "value": 0.66666666666667 - } - ], - "video_start_actions": [ - { - "action_type": "video_view", - "value": 0 - } - ] - } - ], - "paging": { - "cursors": { - "before": "MA==", - "after": "MA==" - } - } -} \ No newline at end of file From dbafda11545322a039daa674f2766e2555739038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Sun, 21 Jun 2015 18:12:55 +0200 Subject: [PATCH 23/28] Ads support for Ad Operations. --- .../social/facebook/api/ads/Ad.java | 110 ++++++ .../social/facebook/api/ads/AdOperations.java | 111 ++++++ .../social/facebook/api/ads/AdSet.java | 4 - .../social/facebook/api/ads/BidType.java | 8 + .../facebook/api/ads/impl/AdTemplate.java | 95 ++++++ .../api/ads/impl/FacebookAdsTemplate.java | 16 +- .../facebook/api/ads/impl/json/AdMixin.java | 83 +++++ .../api/ads/impl/json/AdSetMixin.java | 2 +- .../api/impl/json/FacebookModule.java | 1 + .../facebook/api/ads/AdSetTemplateTest.java | 1 - .../facebook/api/ads/AdTemplateTest.java | 315 ++++++++++++++++++ .../api/ads/CampaignTemplateTest.java | 6 +- .../social/facebook/api/ads/ad-insights.json | 105 ++++++ .../facebook/api/ads/ad-same-account.json | 73 ++++ .../facebook/api/ads/ad-same-ad-set.json | 52 +++ .../facebook/api/ads/ad-same-campaign.json | 73 ++++ .../facebook/api/ads/ad-wrong-status.json | 45 +++ .../social/facebook/api/ads/ad.json | 45 +++ 18 files changed, 1131 insertions(+), 14 deletions(-) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdOperations.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdTemplate.java create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java create mode 100644 spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-insights.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-account.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-ad-set.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-campaign.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-status.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java new file mode 100644 index 000000000..85838c878 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java @@ -0,0 +1,110 @@ +package org.springframework.social.facebook.api.ads; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Date; + +/** + * @author Sebastian Górecki + */ +public class Ad { + private String id; + private AdStatus status; + private String name; + private BidType bidType; + private BidInfo bidInfo; + + private String accountId; + private String adSetId; + private String campaignId; + + private String creativeId; + private Targeting targeting; + + private Date createdTime; + private Date updatedTime; + + public void setStatus(AdStatus status) { + this.status = status; + } + + public void setName(String name) { + this.name = name; + } + + public void setBidInfo(BidInfo bidInfo) { + this.bidInfo = bidInfo; + } + + public void setAdSetId(String adSetId) { + this.adSetId = adSetId; + } + + public void setCreativeId(String creativeId) { + this.creativeId = creativeId; + } + + public String getId() { + return id; + } + + public AdStatus getStatus() { + return status; + } + + public String getName() { + return name; + } + + public BidType getBidType() { + return bidType; + } + + public BidInfo getBidInfo() { + return bidInfo; + } + + public String getAccountId() { + return accountId; + } + + public String getAdSetId() { + return adSetId; + } + + public String getCampaignId() { + return campaignId; + } + + public String getCreativeId() { + return creativeId; + } + + public Targeting getTargeting() { + return targeting; + } + + public Date getCreatedTime() { + return createdTime; + } + + public Date getUpdatedTime() { + return updatedTime; + } + + public enum AdStatus { + ACTIVE, PAUSED, CAMPAIGN_PAUSED, CAMPAIGN_GROUP_PAUSED, CREDIT_CARD_NEEDED, DISABLED, DISAPPROVED, PENDING_REVIEW, + PREAPPROVED, PENDING_BILLING_INFO, ARCHIVED, DELETED, UNKNOWN; + + @JsonCreator + public static AdStatus fromValue(String value) { + for (AdStatus status : AdStatus.values()) { + if (value.equals(status.name())) { + return status; + } + return UNKNOWN; + } + return UNKNOWN; + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdOperations.java new file mode 100644 index 000000000..6b30ef701 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdOperations.java @@ -0,0 +1,111 @@ +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.ApiException; +import org.springframework.social.InsufficientPermissionException; +import org.springframework.social.MissingAuthorizationException; +import org.springframework.social.facebook.api.PagedList; + +/** + * @author Sebastian Górecki + */ +public interface AdOperations { + static final String[] AD_FIELDS = { + "id", "account_id", "adgroup_status", "bid_type", "bid_info", "campaign_id", "campaign_group_id", "created_time", + "creative", "name", "targeting", "updated_time" + }; + + static final String[] AD_INSIGHT_FIELDS = { + "account_id", "account_name", "date_start", "date_stop", "actions_per_impression", "clicks", "unique_clicks", + "cost_per_result", "cost_per_total_action", "cpc", "cost_per_unique_click", "cpm", "cpp", "ctr", "unique_ctr", + "frequency", "impressions", "unique_impressions", "objective", "reach", "result_rate", "results", "roas", + "social_clicks", "unique_social_clicks", "social_impressions", "unique_social_impressions", "social_reach", + "spend", "today_spend", "total_action_value", "total_actions", "total_unique_actions", "actions", + "unique_actions", "cost_per_action_type", "video_start_actions" + }; + + /** + * Get all ads from one ad account. + * + * @param accountId the ID of the ad account + * @return the list of the {@link Ad} objects. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getAccountAds(String accountId); + + /** + * Get all ads from one ad campaign. + * + * @param campaignId the id of the ad campaign. + * @return the list of the {@link Ad} objects. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getCampaignAds(String campaignId); + + /** + * Get all ads from one ad set. + * + * @param adSetId the of of the ad set. + * @return the list of the {@link Ad} objects. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + PagedList getAdSetAds(String adSetId); + + /** + * Get details about an ad. + * + * @param adId the id of an ad. + * @return the {@link Ad} object. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + Ad getAd(String adId); + + /** + * Get the insights from ad object. + * + * @param adId the id of an ad. + * @return the {@link AdInsight} object. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + AdInsight getAdInsight(String adId); + + /** + * Synchronously creates one ad. + * + * @param accountId the ID of the ad account + * @param ad the {@link Ad} object. + * @return the id of created ad. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + String createAd(String accountId, Ad ad); + + /** + * Updates certain fields in an ad. + * + * @param adId the if of an ad. + * @param ad the {@link Ad} object. + * @return true if successful. + */ + boolean updateAd(String adId, Ad ad); + + /** + * Deletes an ad object. + * + * @param adId the if of an ad. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "ads_read" or "ads_management" permission. + * @throws MissingAuthorizationException if FacebookAdsTemplate was not created with an access token. + */ + void deleteAd(String adId); +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java index 047b4de11..b028de007 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java @@ -145,10 +145,6 @@ public Date getUpdatedTime() { return updatedTime; } - public enum BidType { - CPM, CPC, MULTI_PREMIUM, ABSOLUTE_OCPM, CPA, UNKNOWN - } - public enum AdSetStatus { ACTIVE, PAUSED, ARCHIVED, DELETED, CAMPAIGN_GROUP_PAUSED, UNKNOWN } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java new file mode 100644 index 000000000..9f345432b --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java @@ -0,0 +1,8 @@ +package org.springframework.social.facebook.api.ads; + +/** + * @author Sebastian Górecki + */ +public enum BidType { + CPM, CPC, MULTI_PREMIUM, ABSOLUTE_OCPM, CPA, UNKNOWN +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdTemplate.java new file mode 100644 index 000000000..e25eaad18 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdTemplate.java @@ -0,0 +1,95 @@ +package org.springframework.social.facebook.api.ads.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.social.facebook.api.GraphApi; +import org.springframework.social.facebook.api.PagedList; +import org.springframework.social.facebook.api.ads.Ad; +import org.springframework.social.facebook.api.ads.AdInsight; +import org.springframework.social.facebook.api.ads.AdOperations; +import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +/** + * @author Sebastian Górecki + */ +public class AdTemplate extends AbstractFacebookOperations implements AdOperations { + + private final GraphApi graphApi; + private final RestTemplate restTemplate; + private ObjectMapper mapper; + + public AdTemplate(GraphApi graphApi, RestTemplate restTemplate, ObjectMapper mapper, boolean isAuthorizedForUser) { + super(isAuthorizedForUser); + this.graphApi = graphApi; + this.restTemplate = restTemplate; + this.mapper = mapper; + } + + public PagedList getAccountAds(String accountId) { + requireAuthorization(); + return graphApi.fetchConnections(getAdAccountId(accountId), "adgroups", Ad.class, AdOperations.AD_FIELDS); + } + + public PagedList getCampaignAds(String campaignId) { + requireAuthorization(); + return graphApi.fetchConnections(campaignId, "adgroups", Ad.class, AdOperations.AD_FIELDS); + } + + public PagedList getAdSetAds(String adSetId) { + requireAuthorization(); + return graphApi.fetchConnections(adSetId, "adgroups", Ad.class, AdOperations.AD_FIELDS); + } + + public Ad getAd(String adId) { + requireAuthorization(); + return graphApi.fetchObject(adId, Ad.class, AdOperations.AD_FIELDS); + } + + public AdInsight getAdInsight(String adId) { + requireAuthorization(); + PagedList insights = graphApi.fetchConnections(adId, "insights", AdInsight.class, AdOperations.AD_INSIGHT_FIELDS); + return insights.get(0); + } + + public String createAd(String accountId, Ad ad) { + requireAuthorization(); + MultiValueMap data = mapCommonFields(ad); + data.add("campaign_id", ad.getAdSetId()); + return graphApi.publish(getAdAccountId(accountId), "adgroups", data); + } + + public boolean updateAd(String adId, Ad ad) { + requireAuthorization(); + MultiValueMap data = mapCommonFields(ad); + return graphApi.update(adId, data); + } + + public void deleteAd(String adId) { + requireAuthorization(); + restTemplate.delete(GraphApi.GRAPH_API_URL + adId); + } + + private MultiValueMap mapCommonFields(Ad ad) { + MultiValueMap data = new LinkedMultiValueMap(); + if (ad.getName() != null) { + data.add("name", ad.getName()); + } + if (ad.getStatus() != null) { + data.add("adgroup_status", ad.getStatus().name()); + } + if (ad.getBidInfo() != null) { + try { + data.add("bid_info", mapper.writeValueAsString(ad.getBidInfo())); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + if (ad.getCreativeId() != null) { + data.add("creative", "{\"creative_id\": \"" + ad.getCreativeId() + "\"}"); + } + return data; + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java index 3c4079c07..14dc3cd98 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java @@ -1,8 +1,6 @@ package org.springframework.social.facebook.api.ads.impl; import org.springframework.social.facebook.api.ads.*; -import org.springframework.social.facebook.api.ads.impl.AccountTemplate; -import org.springframework.social.facebook.api.ads.impl.CampaignTemplate; import org.springframework.social.facebook.api.impl.FacebookTemplate; /** @@ -16,6 +14,7 @@ public class FacebookAdsTemplate extends FacebookTemplate implements FacebookAds private CampaignOperations campaignOperations; private AdSetOperations adSetOperations; private CreativeOperations creativeOperations; + private AdOperations adOperations; public FacebookAdsTemplate() { super(null); @@ -35,14 +34,23 @@ public CampaignOperations campaignOperations() { return campaignOperations; } - public AdSetOperations adSetOperations() { return adSetOperations; } + public AdSetOperations adSetOperations() { + return adSetOperations; + } + + public CreativeOperations creativeOperations() { + return creativeOperations; + } - public CreativeOperations creativeOperations() { return creativeOperations; } + public AdOperations adOperations() { + return adOperations; + } private void initSubApis() { accountOperations = new AccountTemplate(this, getRestTemplate(), isAuthorized()); campaignOperations = new CampaignTemplate(this, getRestTemplate(), isAuthorized()); adSetOperations = new AdSetTemplate(this, getRestTemplate(), getJsonMessageConverter().getObjectMapper(), isAuthorized()); creativeOperations = new CreativeTemplate(this, getRestTemplate(), isAuthorized()); + adOperations = new AdTemplate(this, getRestTemplate(), getJsonMessageConverter().getObjectMapper(), isAuthorized()); } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java new file mode 100644 index 000000000..3b78921eb --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java @@ -0,0 +1,83 @@ +package org.springframework.social.facebook.api.ads.impl.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.social.facebook.api.ads.Ad; +import org.springframework.social.facebook.api.ads.BidInfo; +import org.springframework.social.facebook.api.ads.BidType; +import org.springframework.social.facebook.api.ads.Targeting; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @author Sebastian Górecki + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AdMixin { + + @JsonProperty("id") + private String id; + + @JsonProperty("adgroup_status") + private Ad.AdStatus status; + + @JsonProperty("name") + private String name; + + @JsonProperty("bid_type") + private BidType bidType; + + @JsonProperty("bid_info") + private BidInfo bidInfo; + + @JsonProperty("account_id") + private String accountId; + + @JsonProperty("campaign_id") + private String adSetId; + + @JsonProperty("campaign_group_id") + private String campaignId; + + @JsonProperty("creative") + @JsonDeserialize(using = CreativeIdDeserializer.class) + private String creativeId; + + @JsonProperty("targeting") + private Targeting targeting; + + @JsonProperty("created_time") + private Date createdTime; + + @JsonProperty("updated_time") + private Date updatedTime; + + private static class CreativeIdDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + if (jp.getCurrentToken() == JsonToken.START_OBJECT) { + try { + Map map = jp.readValueAs(HashMap.class); + return map.get("id"); + } catch (IOException e) { + return null; + } + } + return null; + } + } +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java index 135bc29de..92108ae43 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; -import org.springframework.social.facebook.api.ads.AdSet.BidType; +import org.springframework.social.facebook.api.ads.BidType; import org.springframework.social.facebook.api.ads.BidInfo; import org.springframework.social.facebook.api.ads.Targeting; import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java index dc61e805b..52bb57673 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java @@ -141,5 +141,6 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(AdSet.class, AdSetMixin.class); context.setMixInAnnotations(AdCreative.class, AdCreativeMixin.class); + context.setMixInAnnotations(Ad.class, AdMixin.class); } } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index b210b77a3..622ed4def 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -5,7 +5,6 @@ import org.springframework.social.NotAuthorizedException; import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; -import org.springframework.social.facebook.api.ads.AdSet.BidType; import java.text.DateFormat; import java.text.SimpleDateFormat; diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java new file mode 100644 index 000000000..bc751cc80 --- /dev/null +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java @@ -0,0 +1,315 @@ +package org.springframework.social.facebook.api.ads; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.social.NotAuthorizedException; +import org.springframework.social.facebook.api.PagedList; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.springframework.http.HttpMethod.*; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * @author Sebastian Górecki + */ +public class AdTemplateTest extends AbstractFacebookAdsApiTest { + + @Test + public void getAccountAds() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adgroups?fields=id%2Caccount_id%2Cadgroup_status%2Cbid_type%2Cbid_info%2Ccampaign_id%2Ccampaign_group_id%2Ccreated_time%2Ccreative%2Cname%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-same-account"), MediaType.APPLICATION_JSON)); + + PagedList accountAds = facebookAds.adOperations().getAccountAds("123456789"); + assertEquals(3, accountAds.size()); + assertEquals("101123456789", accountAds.get(0).getId()); + assertEquals("801123456789", accountAds.get(0).getAdSetId()); + assertEquals("701123456789", accountAds.get(0).getCampaignId()); + assertEquals("123456789", accountAds.get(0).getAccountId()); + assertEquals("102123456789", accountAds.get(1).getId()); + assertEquals("802123456789", accountAds.get(1).getAdSetId()); + assertEquals("702123456789", accountAds.get(1).getCampaignId()); + assertEquals("123456789", accountAds.get(1).getAccountId()); + assertEquals("103123456789", accountAds.get(2).getId()); + assertEquals("803123456789", accountAds.get(2).getAdSetId()); + assertEquals("703123456789", accountAds.get(2).getCampaignId()); + assertEquals("123456789", accountAds.get(2).getAccountId()); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAccountAds_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().getAccountAds("123456789"); + } + + @Test + public void getCampaignAds() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/701123456789/adgroups?fields=id%2Caccount_id%2Cadgroup_status%2Cbid_type%2Cbid_info%2Ccampaign_id%2Ccampaign_group_id%2Ccreated_time%2Ccreative%2Cname%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-same-campaign"), MediaType.APPLICATION_JSON)); + + PagedList campaignAds = facebookAds.adOperations().getCampaignAds("701123456789"); + assertEquals(3, campaignAds.size()); + assertEquals("101123456789", campaignAds.get(0).getId()); + assertEquals("801123456789", campaignAds.get(0).getAdSetId()); + assertEquals("701123456789", campaignAds.get(0).getCampaignId()); + assertEquals("102123456789", campaignAds.get(1).getId()); + assertEquals("802123456789", campaignAds.get(1).getAdSetId()); + assertEquals("701123456789", campaignAds.get(1).getCampaignId()); + assertEquals("103123456789", campaignAds.get(2).getId()); + assertEquals("803123456789", campaignAds.get(2).getAdSetId()); + assertEquals("701123456789", campaignAds.get(2).getCampaignId()); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getCampaignAds_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().getCampaignAds("701123456789"); + } + + @Test + public void getAdSetAds() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/800123456789/adgroups?fields=id%2Caccount_id%2Cadgroup_status%2Cbid_type%2Cbid_info%2Ccampaign_id%2Ccampaign_group_id%2Ccreated_time%2Ccreative%2Cname%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-same-ad-set"), MediaType.APPLICATION_JSON)); + + PagedList adSetAds = facebookAds.adOperations().getAdSetAds("800123456789"); + assertEquals(2, adSetAds.size()); + assertEquals("101123456789", adSetAds.get(0).getId()); + assertEquals("800123456789", adSetAds.get(0).getAdSetId()); + assertEquals("700123456789", adSetAds.get(0).getCampaignId()); + + assertEquals("102123456789", adSetAds.get(1).getId()); + assertEquals("800123456789", adSetAds.get(1).getAdSetId()); + assertEquals("701123456789", adSetAds.get(1).getCampaignId()); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdSetAds_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().getAdSetAds("800123456789"); + } + + @Test + public void getAd() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/100123456789?fields=id%2Caccount_id%2Cadgroup_status%2Cbid_type%2Cbid_info%2Ccampaign_id%2Ccampaign_group_id%2Ccreated_time%2Ccreative%2Cname%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad"), MediaType.APPLICATION_JSON)); + + Ad ad = facebookAds.adOperations().getAd("100123456789"); + verifyAd(ad); + assertEquals(Ad.AdStatus.ACTIVE, ad.getStatus()); + mockServer.verify(); + } + + @Test + public void getAd_withWrongStatus() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/100123456789?fields=id%2Caccount_id%2Cadgroup_status%2Cbid_type%2Cbid_info%2Ccampaign_id%2Ccampaign_group_id%2Ccreated_time%2Ccreative%2Cname%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-wrong-status"), MediaType.APPLICATION_JSON)); + + Ad ad = facebookAds.adOperations().getAd("100123456789"); + verifyAd(ad); + assertEquals(Ad.AdStatus.UNKNOWN, ad.getStatus()); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAd_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().getAd("100123456789"); + } + + @Test + public void getAdInsight() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/100123456789/insights?fields=account_id%2Caccount_name%2Cdate_start%2Cdate_stop%2Cactions_per_impression%2Cclicks%2Cunique_clicks%2Ccost_per_result%2Ccost_per_total_action%2Ccpc%2Ccost_per_unique_click%2Ccpm%2Ccpp%2Cctr%2Cunique_ctr%2Cfrequency%2Cimpressions%2Cunique_impressions%2Cobjective%2Creach%2Cresult_rate%2Cresults%2Croas%2Csocial_clicks%2Cunique_social_clicks%2Csocial_impressions%2Cunique_social_impressions%2Csocial_reach%2Cspend%2Ctoday_spend%2Ctotal_action_value%2Ctotal_actions%2Ctotal_unique_actions%2Cactions%2Cunique_actions%2Ccost_per_action_type%2Cvideo_start_actions")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-insights"), MediaType.APPLICATION_JSON)); + + AdInsight insight = facebookAds.adOperations().getAdInsight("100123456789"); + Assert.assertEquals("123456789", insight.getAccountId()); + Assert.assertEquals("Test account name", insight.getAccountName()); + Assert.assertEquals(0.016042780748663, insight.getActionsPerImpression(), EPSILON); + Assert.assertEquals(8, insight.getClicks()); + Assert.assertEquals(5, insight.getUniqueClicks()); + Assert.assertEquals(0.66666666666667, insight.getCostPerResult(), EPSILON); + Assert.assertEquals(0.66666666666667, insight.getCostPerTotalAction(), EPSILON); + Assert.assertEquals(0.25, insight.getCostPerClick(), EPSILON); + Assert.assertEquals(0.4, insight.getCostPerUniqueClick(), EPSILON); + Assert.assertEquals(10.695187165775, insight.getCpm(), EPSILON); + Assert.assertEquals(10.869565217391, insight.getCpp(), EPSILON); + Assert.assertEquals(4.2780748663102, insight.getCtr(), EPSILON); + Assert.assertEquals(2.7173913043478, insight.getUniqueCtr(), EPSILON); + Assert.assertEquals(1.0163043478261, insight.getFrequency(), EPSILON); + Assert.assertEquals(187, insight.getImpressions()); + Assert.assertEquals(184, insight.getUniqueImpressions()); + Assert.assertEquals(184, insight.getReach()); + Assert.assertEquals(1.6042780748663, insight.getResultRate(), EPSILON); + Assert.assertEquals(3, insight.getResults()); + Assert.assertEquals(0, insight.getRoas()); + Assert.assertEquals(0, insight.getSocialClicks()); + Assert.assertEquals(0, insight.getUniqueSocialClicks()); + Assert.assertEquals(0, insight.getSocialImpressions()); + Assert.assertEquals(0, insight.getUniqueSocialImpressions()); + Assert.assertEquals(0, insight.getSocialReach()); + Assert.assertEquals(2, insight.getSpend()); + Assert.assertEquals(0, insight.getTodaySpend()); + Assert.assertEquals(0, insight.getTotalActionValue()); + Assert.assertEquals(3, insight.getTotalActions()); + Assert.assertEquals(2, insight.getTotalUniqueActions()); + Assert.assertEquals(4, insight.getActions().size()); + Assert.assertEquals("comment", insight.getActions().get(0).getActionType()); + Assert.assertEquals(2, insight.getActions().get(0).getValue(), EPSILON); + Assert.assertEquals("post_like", insight.getActions().get(1).getActionType()); + Assert.assertEquals(1, insight.getActions().get(1).getValue(), EPSILON); + Assert.assertEquals("page_engagement", insight.getActions().get(2).getActionType()); + Assert.assertEquals(3, insight.getActions().get(2).getValue(), EPSILON); + Assert.assertEquals("post_engagement", insight.getActions().get(3).getActionType()); + Assert.assertEquals(3, insight.getActions().get(3).getValue(), EPSILON); + Assert.assertEquals(4, insight.getUniqueActions().size()); + Assert.assertEquals("comment", insight.getUniqueActions().get(0).getActionType()); + Assert.assertEquals(1, insight.getUniqueActions().get(0).getValue(), EPSILON); + Assert.assertEquals("post_like", insight.getUniqueActions().get(1).getActionType()); + Assert.assertEquals(1, insight.getUniqueActions().get(1).getValue(), EPSILON); + Assert.assertEquals("page_engagement", insight.getUniqueActions().get(2).getActionType()); + Assert.assertEquals(2, insight.getUniqueActions().get(2).getValue(), EPSILON); + Assert.assertEquals("post_engagement", insight.getUniqueActions().get(3).getActionType()); + Assert.assertEquals(2, insight.getUniqueActions().get(3).getValue(), EPSILON); + Assert.assertEquals(4, insight.getCostPerActionType().size()); + Assert.assertEquals("comment", insight.getCostPerActionType().get(0).getActionType()); + Assert.assertEquals(1, insight.getCostPerActionType().get(0).getValue(), EPSILON); + Assert.assertEquals("post_like", insight.getCostPerActionType().get(1).getActionType()); + Assert.assertEquals(2, insight.getCostPerActionType().get(1).getValue(), EPSILON); + Assert.assertEquals("page_engagement", insight.getCostPerActionType().get(2).getActionType()); + Assert.assertEquals(0.66666666666667, insight.getCostPerActionType().get(2).getValue(), EPSILON); + Assert.assertEquals("post_engagement", insight.getCostPerActionType().get(3).getActionType()); + Assert.assertEquals(0.66666666666667, insight.getCostPerActionType().get(3).getValue(), EPSILON); + Assert.assertEquals(1, insight.getVideoStartActions().size()); + Assert.assertEquals("video_view", insight.getVideoStartActions().get(0).getActionType()); + Assert.assertEquals(0, insight.getVideoStartActions().get(0).getValue(), EPSILON); + + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void getAdInsight_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().getAdInsight("100123456789"); + } + + @Test + public void createAd() throws Exception { + String requestBody = "name=Test+ad&adgroup_status=PAUSED&creative=%7B%22creative_id%22%3A+%22900123456789%22%7D&campaign_id=800123456789"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adgroups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"100123456789\"}", MediaType.APPLICATION_JSON)); + + Ad ad = new Ad(); + ad.setName("Test ad"); + ad.setStatus(Ad.AdStatus.PAUSED); + ad.setAdSetId("800123456789"); + ad.setCreativeId("900123456789"); + assertEquals("100123456789", facebookAds.adOperations().createAd("123456789", ad)); + mockServer.verify(); + } + + @Test + public void createAd_withBidInfo() throws Exception { + String requestBody = "name=Test+ad&adgroup_status=PAUSED&bid_info=%7B%22REACH%22%3A%2211%22%2C%22ACTIONS%22%3A%2210%22%2C%22SOCIAL%22%3A%2250%22%2C%22CLICKS%22%3A%2212%22%7D&creative=%7B%22creative_id%22%3A+%22900123456789%22%7D&campaign_id=800123456789"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adgroups")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"100123456789\"}", MediaType.APPLICATION_JSON)); + + Ad ad = new Ad(); + ad.setName("Test ad"); + ad.setStatus(Ad.AdStatus.PAUSED); + ad.setAdSetId("800123456789"); + ad.setCreativeId("900123456789"); + BidInfo bidInfo = new BidInfo(); + bidInfo.put("ACTIONS", "10"); + bidInfo.put("REACH", "11"); + bidInfo.put("CLICKS", "12"); + bidInfo.put("SOCIAL", "50"); + ad.setBidInfo(bidInfo); + assertEquals("100123456789", facebookAds.adOperations().createAd("123456789", ad)); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void createAd_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().createAd("123456789", new Ad()); + } + + @Test + public void updateAd() throws Exception { + String requestBody = "name=Updated+Ad&adgroup_status=ARCHIVED&bid_info=%7B%22CLICKS%22%3A%22500%22%7D"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/100123456789")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); + + Ad ad = new Ad(); + ad.setStatus(Ad.AdStatus.ARCHIVED); + ad.setName("Updated Ad"); + BidInfo bidInfo = new BidInfo(); + bidInfo.put("CLICKS", "500"); + ad.setBidInfo(bidInfo); + assertTrue(facebookAds.adOperations().updateAd("100123456789", ad)); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void updateAd_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().updateAd("100123456789", new Ad()); + } + + @Test + public void deleteAd() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/100123456789")) + .andExpect(method(DELETE)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess("{\"success\": true}", MediaType.APPLICATION_JSON)); + facebookAds.adOperations().deleteAd("100123456789"); + mockServer.verify(); + } + + @Test(expected = NotAuthorizedException.class) + public void deleteAd_unauthorized() throws Exception { + unauthorizedFacebookAds.adOperations().deleteAd("100123456789"); + } + + private void verifyAd(Ad ad) { + assertEquals("100123456789", ad.getId()); + assertEquals("123456789", ad.getAccountId()); + assertEquals(BidType.ABSOLUTE_OCPM, ad.getBidType()); + assertEquals("800123456789", ad.getAdSetId()); + assertEquals("700123456789", ad.getCampaignId()); + assertEquals(toDate("2015-04-10T09:28:54+0200"), ad.getCreatedTime()); + assertEquals("900123456789", ad.getCreativeId()); + assertEquals("Test ad name", ad.getName()); + assertEquals(Integer.valueOf(18), ad.getTargeting().getAgeMin()); + assertEquals(Integer.valueOf(20), ad.getTargeting().getAgeMax()); + assertEquals(6004854404172L, ad.getTargeting().getBehaviors().get(0).getId()); + assertEquals("Technology late adopters", ad.getTargeting().getBehaviors().get(0).getName()); + assertEquals(Targeting.Gender.MALE, ad.getTargeting().getGenders().get(0)); + assertEquals("PL", ad.getTargeting().getGeoLocations().getCountries().get(0)); + assertEquals(TargetingLocation.LocationType.HOME, ad.getTargeting().getGeoLocations().getLocationTypes().get(0)); + assertEquals(TargetingLocation.LocationType.RECENT, ad.getTargeting().getGeoLocations().getLocationTypes().get(1)); + assertEquals(6003629266583L, ad.getTargeting().getInterests().get(0).getId()); + assertEquals("Hard drives", ad.getTargeting().getInterests().get(0).getName()); + assertEquals(Targeting.PageType.FEED, ad.getTargeting().getPageTypes().get(0)); + assertEquals(toDate("2015-04-10T13:32:09+0200"), ad.getUpdatedTime()); + } +} diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index 812ce38bd..a5f7b4838 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -6,8 +6,6 @@ import org.springframework.social.NotAuthorizedException; import org.springframework.social.facebook.api.InvalidCampaignStatusException; import org.springframework.social.facebook.api.PagedList; -import org.springframework.social.facebook.api.ads.AbstractFacebookAdsApiTest; -import org.springframework.social.facebook.api.ads.AdCampaign; import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; @@ -111,7 +109,7 @@ public void getAdCampaignSets() throws Exception { PagedList adSets = facebookAds.campaignOperations().getAdCampaignSets("600123456789"); assertEquals(2, adSets.size()); assertEquals("123456789", adSets.get(0).getAccountId()); - assertEquals(AdSet.BidType.ABSOLUTE_OCPM, adSets.get(0).getBidType()); + assertEquals(BidType.ABSOLUTE_OCPM, adSets.get(0).getBidType()); assertEquals(37407, adSets.get(0).getBudgetRemaining()); assertEquals("600123456789", adSets.get(0).getCampaignId()); assertEquals(AdSet.AdSetStatus.PAUSED, adSets.get(0).getStatus()); @@ -130,7 +128,7 @@ public void getAdCampaignSets() throws Exception { assertEquals(toDate("2015-05-27T11:58:34+0200"), adSets.get(0).getUpdatedTime()); assertEquals("123456789", adSets.get(1).getAccountId()); - assertEquals(AdSet.BidType.ABSOLUTE_OCPM, adSets.get(1).getBidType()); + assertEquals(BidType.ABSOLUTE_OCPM, adSets.get(1).getBidType()); assertEquals(0, adSets.get(1).getBudgetRemaining()); assertEquals("600123456789", adSets.get(1).getCampaignId()); assertEquals(AdSet.AdSetStatus.ACTIVE, adSets.get(1).getStatus()); diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-insights.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-insights.json new file mode 100644 index 000000000..2b40792de --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-insights.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "account_id": "123456789", + "account_name": "Test account name", + "date_start": "2015-04-12", + "date_stop": "2015-04-13", + "actions_per_impression": 0.016042780748663, + "clicks": 8, + "unique_clicks": 5, + "cost_per_result": 0.66666666666667, + "cost_per_total_action": 0.66666666666667, + "cpc": 0.25, + "cost_per_unique_click": 0.4, + "cpm": 10.695187165775, + "cpp": 10.869565217391, + "ctr": 4.2780748663102, + "unique_ctr": 2.7173913043478, + "frequency": 1.0163043478261, + "impressions": "187", + "unique_impressions": 184, + "objective": "POST_ENGAGEMENT", + "reach": 184, + "result_rate": 1.6042780748663, + "results": 3, + "roas": 0, + "social_clicks": 0, + "unique_social_clicks": 0, + "social_impressions": 0, + "unique_social_impressions": 0, + "social_reach": 0, + "spend": 2, + "today_spend": 0, + "total_action_value": 0, + "total_actions": 3, + "total_unique_actions": 2, + "actions": [ + { + "action_type": "comment", + "value": 2 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 3 + }, + { + "action_type": "post_engagement", + "value": 3 + } + ], + "unique_actions": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 1 + }, + { + "action_type": "page_engagement", + "value": 2 + }, + { + "action_type": "post_engagement", + "value": 2 + } + ], + "cost_per_action_type": [ + { + "action_type": "comment", + "value": 1 + }, + { + "action_type": "post_like", + "value": 2 + }, + { + "action_type": "page_engagement", + "value": 0.66666666666667 + }, + { + "action_type": "post_engagement", + "value": 0.66666666666667 + } + ], + "video_start_actions": [ + { + "action_type": "video_view", + "value": 0 + } + ] + } + ], + "paging": { + "cursors": { + "before": "MA==", + "after": "MA==" + } + } +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-account.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-account.json new file mode 100644 index 000000000..6f2fea91c --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-account.json @@ -0,0 +1,73 @@ +{ + "data": [ + { + "id": "101123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "801123456789", + "campaign_group_id": "701123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name1", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + }, + { + "id": "102123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "802123456789", + "campaign_group_id": "702123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name2", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + }, + { + "id": "103123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "803123456789", + "campaign_group_id": "703123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name3", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + } + ], + "paging": { + "cursors": { + "before": "NjAyMjEyODA0OTQ1MA==", + "after": "NjAyMjEyODA0OTQ1MA==" + } + } +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-ad-set.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-ad-set.json new file mode 100644 index 000000000..b472825e6 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-ad-set.json @@ -0,0 +1,52 @@ +{ + "data": [ + { + "id": "101123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "800123456789", + "campaign_group_id": "700123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name1", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + }, + { + "id": "102123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "800123456789", + "campaign_group_id": "701123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name2", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + } + ], + "paging": { + "cursors": { + "before": "NjAyMjEyODA0OTQ1MA==", + "after": "NjAyMjEyODA0OTQ1MA==" + } + } +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-campaign.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-campaign.json new file mode 100644 index 000000000..99a36f3ce --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-same-campaign.json @@ -0,0 +1,73 @@ +{ + "data": [ + { + "id": "101123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "801123456789", + "campaign_group_id": "701123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name1", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + }, + { + "id": "102123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "802123456789", + "campaign_group_id": "701123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name2", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + }, + { + "id": "103123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "803123456789", + "campaign_group_id": "701123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name3", + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "updated_time": "2015-04-10T13:32:09+0200" + } + ], + "paging": { + "cursors": { + "before": "NjAyMjEyODA0OTQ1MA==", + "after": "NjAyMjEyODA0OTQ1MA==" + } + } +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-status.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-status.json new file mode 100644 index 000000000..e71a4d52b --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-status.json @@ -0,0 +1,45 @@ +{ + "id": "100123456789", + "account_id": "123456789", + "adgroup_status": "WRONG_STATUS", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "800123456789", + "campaign_group_id": "700123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name", + "targeting": { + "age_max": 20, + "age_min": 18, + "behaviors": [ + { + "id": "6004854404172", + "name": "Technology late adopters" + } + ], + "genders": [ + 1 + ], + "geo_locations": { + "countries": [ + "PL" + ], + "location_types": [ + "home", + "recent" + ] + }, + "interests": [ + { + "id": "6003629266583", + "name": "Hard drives" + } + ], + "page_types": [ + "feed" + ] + }, + "updated_time": "2015-04-10T13:32:09+0200" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad.json new file mode 100644 index 000000000..8842790a9 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad.json @@ -0,0 +1,45 @@ +{ + "id": "100123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "ABSOLUTE_OCPM", + "campaign_id": "800123456789", + "campaign_group_id": "700123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name", + "targeting": { + "age_max": 20, + "age_min": 18, + "behaviors": [ + { + "id": "6004854404172", + "name": "Technology late adopters" + } + ], + "genders": [ + 1 + ], + "geo_locations": { + "countries": [ + "PL" + ], + "location_types": [ + "home", + "recent" + ] + }, + "interests": [ + { + "id": "6003629266583", + "name": "Hard drives" + } + ], + "page_types": [ + "feed" + ] + }, + "updated_time": "2015-04-10T13:32:09+0200" +} \ No newline at end of file From f3b1e999dd58fe33cdfb55f8116b5ecefefda1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Sun, 21 Jun 2015 19:36:41 +0200 Subject: [PATCH 24/28] Improve Jackson mapping of enums. --- .../social/facebook/api/ads/Ad.java | 3 +- .../social/facebook/api/ads/AdAccount.java | 69 +++++++++++-- .../social/facebook/api/ads/AdCampaign.java | 40 ++++++-- .../social/facebook/api/ads/AdCreative.java | 12 ++- .../social/facebook/api/ads/AdSet.java | 13 ++- .../social/facebook/api/ads/AdUser.java | 48 +++++++++- .../social/facebook/api/ads/BidType.java | 16 +++- .../social/facebook/api/ads/Targeting.java | 10 +- .../facebook/api/ads/TargetingLocation.java | 2 +- .../api/ads/impl/AccountTemplate.java | 21 +--- .../ads/impl/json/AdAccountGroupMixin.java | 7 +- .../api/ads/impl/json/AdAccountMixin.java | 86 +---------------- .../api/ads/impl/json/AdCampaignMixin.java | 58 ++--------- .../api/ads/impl/json/AdCreativeMixin.java | 31 +++--- .../ads/impl/json/AdInsightActionMixin.java | 10 +- .../api/ads/impl/json/AdInsightMixin.java | 96 +++++++++---------- .../facebook/api/ads/impl/json/AdMixin.java | 31 +++--- .../api/ads/impl/json/AdSetMixin.java | 68 ++++--------- .../api/ads/impl/json/AdUserMixin.java | 85 +--------------- .../api/ads/impl/json/BidInfoMixin.java | 11 --- .../impl/json/TargetingCityEntryMixin.java | 9 +- .../ads/impl/json/TargetingEntryMixin.java | 6 +- .../ads/impl/json/TargetingLocationMixin.java | 43 +++------ .../json/TargetingLocationSerializer.java | 2 - .../api/ads/impl/json/TargetingMixin.java | 53 ++++------ .../api/impl/json/FacebookModule.java | 1 - .../facebook/api/ads/AccountTemplateTest.java | 19 ++-- .../facebook/api/ads/AdSetTemplateTest.java | 31 ++++++ .../facebook/api/ads/AdTemplateTest.java | 17 +++- .../api/ads/CampaignTemplateTest.java | 22 ++++- .../ads/ad-campaign-empty-buying-type.json | 8 ++ .../api/ads/ad-set-wrong-bid-type.json | 23 +++++ .../facebook/api/ads/ad-set-wrong-status.json | 23 +++++ .../facebook/api/ads/ad-wrong-bid-type.json | 45 +++++++++ 34 files changed, 525 insertions(+), 494 deletions(-) delete mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-empty-buying-type.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-bid-type.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-status.json create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-bid-type.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java index 85838c878..a171dc089 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Ad.java @@ -99,10 +99,9 @@ public enum AdStatus { @JsonCreator public static AdStatus fromValue(String value) { for (AdStatus status : AdStatus.values()) { - if (value.equals(status.name())) { + if (status.name().equals(value)) { return status; } - return UNKNOWN; } return UNKNOWN; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccount.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccount.java index abe221d29..d67517c50 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccount.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdAccount.java @@ -1,5 +1,7 @@ package org.springframework.social.facebook.api.ads; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; import org.springframework.social.facebook.api.FacebookObject; import java.util.Date; @@ -27,7 +29,7 @@ public class AdAccount extends FacebookObject { private String businessStreet; private String businessStreet2; private String businessZip; - private List capabilities; + private List capabilities; private Date createdTime; private String currency; private String dailySpendLimit; @@ -107,7 +109,7 @@ public String getBusinessZip() { return businessZip; } - public List getCapabilities() { + public List getCapabilities() { return capabilities; } @@ -192,22 +194,75 @@ public TaxStatus getTaxStatus() { } public enum AccountStatus { - ACTIVE, DISABLED, UNSETTLED, PENDING_REVIEW, IN_GRACE_PERIOD, TEMPORARILY_UNAVAILABLE, PENDING_CLOSURE, UNKNOWN + ACTIVE(1), DISABLED(2), UNSETTLED(3), PENDING_REVIEW(7), IN_GRACE_PERIOD(9), TEMPORARILY_UNAVAILABLE(101), + PENDING_CLOSURE(100), UNKNOWN(0); + + private final int value; + + AccountStatus(int value) { + this.value = value; + } + + @JsonCreator + public static AccountStatus fromValue(int value) { + for (AccountStatus status : AccountStatus.values()) { + if (status.getValue() == value) { + return status; + } + } + return UNKNOWN; + } + + @JsonGetter + public int getValue() { + return value; + } } - public enum Capabilities { + public enum Capability { BULK_ACCOUNT, CAN_CREATE_LOOKALIKES_WITH_CUSTOM_RATIO, CAN_USE_CONVERSION_LOOKALIKES, CAN_USE_MOBILE_EXTERNAL_PAGE_TYPE_FOR_LPP, CAN_USE_REACH_AND_FREQUENCY, CUSTOM_CLUSTER_SHARING, DIRECT_SALES, HAS_AD_SET_TARGETING, HAS_AVAILABLE_PAYMENT_METHODS, HOLDOUT_VIEW_TAGS, MOBILE_ADVERTISER_ID_UPLOAD, MOBILE_APP_REENGAGEMENT_ADS, MOBILE_APP_VIDEO_ADS, NEKO_DESKTOP_CANVAS_APP_ADS, NEW_CAMPAIGN_STRUCTURE, PREMIUM, VIEW_TAGS, PRORATED_BUDGET, OFFSITE_CONVERSION_HIGH_BID, CAN_USE_MOBILE_EXTERNAL_PAGE_TYPE, CAN_USE_OLD_AD_TYPES, CAN_USE_VIDEO_METRICS_BREAKDOWN, ADS_CF_INSTORE_DAILY_BUDGET, AD_SET_PROMOTED_OBJECT_APP, AD_SET_PROMOTED_OBJECT_OFFER, AD_SET_PROMOTED_OBJECT_PAGE, AD_SET_PROMOTED_OBJECT_PIXEL, - CONNECTIONS_UI_V2, LOOKALIKE_AUDIENCE, CUSTOM_AUDIENCES_OPT_OUT_LINK, CUSTOM_AUDIENCES_FOLDERS, UNKNOWN + CONNECTIONS_UI_V2, LOOKALIKE_AUDIENCE, CUSTOM_AUDIENCES_OPT_OUT_LINK, CUSTOM_AUDIENCES_FOLDERS, UNKNOWN; + + @JsonCreator + public static Capability fromValue(String value) { + for (Capability capability : Capability.values()) { + if (capability.name().equals(value)) { + return capability; + } + } + return UNKNOWN; + } } public enum TaxStatus { - UNKNOWN, VAT_NOT_REQUIRED_US_CA, VAT_INFORMATION_REQUIRED, VAT_INFORMATION_SUBMITTED, OFFLINE_VAT_VALIDATION_FAILED, - ACCOUNT_IS_PERSONAL_ACCOUNT + VAT_NOT_REQUIRED_US_CA(1), VAT_INFORMATION_REQUIRED(2), VAT_INFORMATION_SUBMITTED(3), OFFLINE_VAT_VALIDATION_FAILED(4), + ACCOUNT_IS_PERSONAL_ACCOUNT(5), UNKNOWN(0); + + private final int value; + + TaxStatus(int value) { + this.value = value; + } + + @JsonCreator + public static TaxStatus fromValue(int value) { + for (TaxStatus status : TaxStatus.values()) { + if (status.getValue() == value) { + return status; + } + } + return UNKNOWN; + } + + @JsonGetter + public int getValue() { + return value; + } } public class AgencyClientDeclaration { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java index 4ea91aee0..4f2eef78c 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCampaign.java @@ -1,5 +1,6 @@ package org.springframework.social.facebook.api.ads; +import com.fasterxml.jackson.annotation.JsonCreator; import org.springframework.social.facebook.api.FacebookObject; /** @@ -16,9 +17,6 @@ public class AdCampaign extends FacebookObject { private CampaignObjective objective; private int spendCap; - public AdCampaign() { - } - public CampaignStatus getStatus() { return status; } @@ -68,16 +66,46 @@ public void setSpendCap(int spendCap) { } public enum BuyingType { - AUCTION, FIXED_CPM, RESERVED, MIXED, UNKNOWN + AUCTION, FIXED_CPM, RESERVED, MIXED, UNKNOWN; + + @JsonCreator + public static BuyingType fromValue(String value) { + for (BuyingType type : BuyingType.values()) { + if (type.name().equals(value)) { + return type; + } + } + return UNKNOWN; + } } public enum CampaignStatus { - ACTIVE, PAUSED, ARCHIVED, DELETED, UNKNOWN + ACTIVE, PAUSED, ARCHIVED, DELETED, UNKNOWN; + + @JsonCreator + public static CampaignStatus fromValue(String value) { + for (CampaignStatus status : CampaignStatus.values()) { + if (status.name().equals(value)) { + return status; + } + } + return UNKNOWN; + } } public enum CampaignObjective { CANVAS_APP_ENGAGEMENT, CANVAS_APP_INSTALLS, EVENT_RESPONSES, LOCAL_AWARENESS, MOBILE_APP_ENGAGEMENT, MOBILE_APP_INSTALLS, NONE, OFFER_CLAIMS, PAGE_LIKES, POST_ENGAGEMENT, VIDEO_VIEWS, WEBSITE_CLICKS, - WEBSITE_CONVERSIONS, UNKNOWN + WEBSITE_CONVERSIONS, UNKNOWN; + + @JsonCreator + public static CampaignObjective fromValue(String value) { + for (CampaignObjective objective : CampaignObjective.values()) { + if (objective.name().equals(value)) { + return objective; + } + } + return UNKNOWN; + } } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java index 13a9c35cb..759691904 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdCreative.java @@ -134,9 +134,11 @@ public enum AdCreativeType { PAGE, DOMAIN, EVENT, STORE_ITEM, OFFER, SHARE, PHOTO, STATUS, VIDEO, APPLICATION, INVALID, UNKNOWN; @JsonCreator - public static AdCreativeType forValue(String value) { + public static AdCreativeType fromValue(String value) { for (AdCreativeType type : AdCreativeType.values()) { - if (type.name().equals(value)) return type; + if (type.name().equals(value)) { + return type; + } } return UNKNOWN; } @@ -147,9 +149,11 @@ public enum AdCreativeStatus { CAMPAIGN_PAUSED, ADGROUP_PAUSED, CAMPAIGN_GROUP_PAUSED, ARCHIVED, UNKNOWN; @JsonCreator - public static AdCreativeStatus forValue(String value) { + public static AdCreativeStatus fromValue(String value) { for (AdCreativeStatus status : AdCreativeStatus.values()) { - if (status.name().equals(value)) return status; + if (status.name().equals(value)) { + return status; + } } return UNKNOWN; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java index b028de007..420c382cc 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java @@ -1,5 +1,6 @@ package org.springframework.social.facebook.api.ads; +import com.fasterxml.jackson.annotation.JsonCreator; import org.springframework.social.facebook.api.FacebookObject; import java.util.Date; @@ -146,6 +147,16 @@ public Date getUpdatedTime() { } public enum AdSetStatus { - ACTIVE, PAUSED, ARCHIVED, DELETED, CAMPAIGN_GROUP_PAUSED, UNKNOWN + ACTIVE, PAUSED, ARCHIVED, DELETED, CAMPAIGN_GROUP_PAUSED, UNKNOWN; + + @JsonCreator + public static AdSetStatus fromValue(String value) { + for (AdSetStatus status : AdSetStatus.values()) { + if (status.name().equals(value)) { + return status; + } + } + return UNKNOWN; + } } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdUser.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdUser.java index 0386a6bfd..9e4da7279 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdUser.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdUser.java @@ -1,5 +1,7 @@ package org.springframework.social.facebook.api.ads; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; import org.springframework.social.facebook.api.FacebookObject; import java.util.List; @@ -32,10 +34,52 @@ public AdUserRole getRole() { } public enum AdUserPermission { - ACCOUNT_ADMIN, ADMANAGER_READ, ADMANAGER_WRITE, BILLING_READ, BILLING_WRITE, REPORTS, UNKNOWN + ACCOUNT_ADMIN(1), ADMANAGER_READ(2), ADMANAGER_WRITE(3), BILLING_READ(4), BILLING_WRITE(5), REPORTS(7), UNKNOWN(0); + + private final int value; + + AdUserPermission(int value) { + this.value = value; + } + + @JsonCreator + public static AdUserPermission forValue(int value) { + for (AdUserPermission permission : AdUserPermission.values()) { + if (permission.getValue() == value) { + return permission; + } + } + return UNKNOWN; + } + + @JsonGetter + public int getValue() { + return value; + } } public enum AdUserRole { - ADMINISTRATOR, ADVERTISER, ANALYST, SALES, UNKNOWN + ADMINISTRATOR(1001), ADVERTISER(1002), ANALYST(1003), SALES(1004), UNKNOWN(0); + + private final int value; + + AdUserRole(int value) { + this.value = value; + } + + @JsonCreator + public static AdUserRole forValue(int value) { + for (AdUserRole role : AdUserRole.values()) { + if (role.getValue() == value) { + return role; + } + } + return UNKNOWN; + } + + @JsonGetter + public int getValue() { + return value; + } } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java index 9f345432b..2f1934b37 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidType.java @@ -1,8 +1,22 @@ package org.springframework.social.facebook.api.ads; +import com.fasterxml.jackson.annotation.JsonCreator; + /** + * Enum used in Ad and AdSet objects. + * * @author Sebastian Górecki */ public enum BidType { - CPM, CPC, MULTI_PREMIUM, ABSOLUTE_OCPM, CPA, UNKNOWN + CPM, CPC, MULTI_PREMIUM, ABSOLUTE_OCPM, CPA, UNKNOWN; + + @JsonCreator + public static BidType fromValue(String value) { + for (BidType type : BidType.values()) { + if (type.name().equals(value)) { + return type; + } + } + return UNKNOWN; + } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java index dc135f5f3..1bf968cb7 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/Targeting.java @@ -205,7 +205,7 @@ public enum Gender { } @JsonCreator - public static Gender forValue(int value) { + public static Gender fromValue(int value) { for (Gender gender : Gender.values()) { if (gender.getValue() == value) return gender; } @@ -230,7 +230,7 @@ public enum RelationshipStatus { } @JsonCreator - public static RelationshipStatus forValue(int value) { + public static RelationshipStatus fromValue(int value) { for (RelationshipStatus status : RelationshipStatus.values()) { if (status.getValue() == value) return status; } @@ -253,7 +253,7 @@ public enum InterestedIn { } @JsonCreator - public static InterestedIn forValue(int value) { + public static InterestedIn fromValue(int value) { for (InterestedIn interestedIn : InterestedIn.values()) { if (interestedIn.getValue() == value) return interestedIn; } @@ -279,7 +279,7 @@ public enum PageType { } @JsonCreator - public static PageType forValue(String value) { + public static PageType fromValue(String value) { for (PageType type : PageType.values()) { if (type.getValue().equals(value)) return type; } @@ -304,7 +304,7 @@ public enum EducationStatus { } @JsonCreator - public static EducationStatus forValue(int value) { + public static EducationStatus fromValue(int value) { for (EducationStatus status : EducationStatus.values()) { if (status.getValue() == value) return status; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java index d3f4eeaef..9ed228033 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/TargetingLocation.java @@ -65,7 +65,7 @@ public enum LocationType { } @JsonCreator - public static LocationType forValue(String value) { + public static LocationType fromValue(String value) { for (LocationType type : LocationType.values()) { if (type.getValue().equals(value)) return type; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java index 98fe9c684..0f522e5ba 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java @@ -1,6 +1,7 @@ package org.springframework.social.facebook.api.ads.impl; -import org.springframework.social.facebook.api.*; +import org.springframework.social.facebook.api.GraphApi; +import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.*; import org.springframework.social.facebook.api.ads.AdUser.AdUserRole; import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; @@ -46,7 +47,7 @@ public void addUserToAdAccount(String accountId, String userId, AdUserRole role) requireAuthorization(); MultiValueMap map = new LinkedMultiValueMap(); map.set("uid", userId); - map.set("role", String.valueOf(serializeRole(role))); + map.set("role", String.valueOf(role.getValue())); graphApi.post(getAdAccountId(accountId) + "/users", "", map); } @@ -72,20 +73,4 @@ public boolean updateAdAccount(String accountId, AdAccount adAccount) { } return graphApi.update(getAdAccountId(accountId), map); } - - private int serializeRole(AdUserRole role) { - switch (role) { - case ADMINISTRATOR: - return 1001; - case ADVERTISER: - return 1002; - case ANALYST: - return 1003; - case SALES: - return 1004; - case UNKNOWN: - default: - return 0; - } - } } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountGroupMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountGroupMixin.java index f8ab2a63d..40e68e7b7 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountGroupMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountGroupMixin.java @@ -6,17 +6,18 @@ /** * Annotated mixin to add Jackson annotations to AdAccountGroup. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class AdAccountGroupMixin extends FacebookObjectMixin { @JsonProperty("account_group_id") - private String id; + String id; @JsonProperty("name") - private String name; + String name; @JsonProperty("status") - private int status; + int status; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java index ada9d6ff0..ba5e4925f 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdAccountMixin.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.springframework.social.facebook.api.ads.AdAccount.AccountStatus; import org.springframework.social.facebook.api.ads.AdAccount.AgencyClientDeclaration; -import org.springframework.social.facebook.api.ads.AdAccount.Capabilities; +import org.springframework.social.facebook.api.ads.AdAccount.Capability; import org.springframework.social.facebook.api.ads.AdAccount.TaxStatus; import org.springframework.social.facebook.api.ads.AdAccountGroup; import org.springframework.social.facebook.api.ads.AdUser; @@ -41,7 +41,6 @@ public abstract class AdAccountMixin extends FacebookObjectMixin { long accountId; @JsonProperty("account_status") - @JsonDeserialize(using = AccountStatusDeserializer.class) AccountStatus status; @JsonProperty("age") @@ -78,8 +77,7 @@ public abstract class AdAccountMixin extends FacebookObjectMixin { String businessZip; @JsonProperty("capabilities") - @JsonDeserialize(using = CapabilitiesListDeserializer.class) - List capabilities; + List capabilities; @JsonProperty("created_time") Date createdTime; @@ -134,65 +132,8 @@ public abstract class AdAccountMixin extends FacebookObjectMixin { List users; @JsonProperty("tax_id_status") - @JsonDeserialize(using = TaxStatusDeserializer.class) TaxStatus taxStatus; - private static class AccountStatusDeserializer extends JsonDeserializer { - @Override - public AccountStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - int status = jp.getIntValue(); - switch (status) { - case 1: - return AccountStatus.ACTIVE; - case 2: - return AccountStatus.DISABLED; - case 3: - return AccountStatus.UNSETTLED; - case 7: - return AccountStatus.PENDING_REVIEW; - case 9: - return AccountStatus.IN_GRACE_PERIOD; - case 101: - return AccountStatus.TEMPORARILY_UNAVAILABLE; - case 100: - return AccountStatus.PENDING_CLOSURE; - default: - return AccountStatus.UNKNOWN; - } - } catch (IOException e) { - return AccountStatus.UNKNOWN; - } - } - } - - private static class TaxStatusDeserializer extends JsonDeserializer { - @Override - public TaxStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - int status = jp.getIntValue(); - switch (status) { - case 0: - return TaxStatus.UNKNOWN; - case 1: - return TaxStatus.VAT_NOT_REQUIRED_US_CA; - case 2: - return TaxStatus.VAT_INFORMATION_REQUIRED; - case 3: - return TaxStatus.VAT_INFORMATION_SUBMITTED; - case 4: - return TaxStatus.OFFLINE_VAT_VALIDATION_FAILED; - case 5: - return TaxStatus.ACCOUNT_IS_PERSONAL_ACCOUNT; - default: - return TaxStatus.UNKNOWN; - } - } catch (IOException e) { - return TaxStatus.UNKNOWN; - } - } - } - private static class AdUserListDeserializer extends JsonDeserializer> { @SuppressWarnings("unchecked") @Override @@ -216,29 +157,6 @@ public List deserialize(JsonParser jp, DeserializationContext ctxt) thro } } - private static class CapabilitiesListDeserializer extends JsonDeserializer> { - @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - if (jp.getCurrentToken() == JsonToken.START_ARRAY) { - List capabilities = new ArrayList(); - try { - while (jp.nextToken() != JsonToken.END_ARRAY) { - String capability = jp.getValueAsString(); - try { - capabilities.add(Capabilities.valueOf(capability.toUpperCase())); - } catch (IllegalArgumentException e) { - capabilities.add(Capabilities.UNKNOWN); - } - } - return capabilities; - } catch (IOException e) { - return Collections.emptyList(); - } - } - return Collections.emptyList(); - } - } - @JsonIgnoreProperties(ignoreUnknown = true) public abstract class AgencyClientDeclarationMixin extends FacebookObjectMixin { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java index 069b560fd..bca215d6f 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCampaignMixin.java @@ -2,18 +2,11 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; -import java.io.IOException; - /** * Annotated mixin to add Jackson annotations to AdCampaign. * @@ -23,60 +16,23 @@ abstract public class AdCampaignMixin extends FacebookObjectMixin { @JsonProperty("id") - private String id; + String id; @JsonProperty("account_id") - private String accountId; + String accountId; @JsonProperty("buying_type") - @JsonDeserialize(using = BuyingTypeDeserializer.class) - private BuyingType buyingType; + BuyingType buyingType; @JsonProperty("campaign_group_status") - @JsonDeserialize(using = CampaignStatusDeserializer.class) - private CampaignStatus status; + CampaignStatus status; @JsonProperty("name") - private String name; + String name; @JsonProperty("objective") - @JsonDeserialize(using = CampaignObjectiveDeserializer.class) - private CampaignObjective objective; + CampaignObjective objective; @JsonProperty("spend_cap") - private int spendCap; - - - private static class BuyingTypeDeserializer extends JsonDeserializer { - @Override - public BuyingType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - return BuyingType.valueOf(jp.getValueAsString().toUpperCase()); - } catch (IllegalArgumentException e) { - return BuyingType.UNKNOWN; - } - } - } - - private static class CampaignStatusDeserializer extends JsonDeserializer { - @Override - public CampaignStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - return CampaignStatus.valueOf(jp.getValueAsString().toUpperCase()); - } catch (IllegalArgumentException e) { - return CampaignStatus.UNKNOWN; - } - } - } - - private static class CampaignObjectiveDeserializer extends JsonDeserializer { - @Override - public CampaignObjective deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - return CampaignObjective.valueOf(jp.getValueAsString().toUpperCase()); - } catch (IllegalArgumentException e) { - return CampaignObjective.UNKNOWN; - } - } - } + int spendCap; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java index 04ec0ecd1..e6bba4f6e 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdCreativeMixin.java @@ -6,51 +6,52 @@ import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; /** + * Annotated mixin to add Jackson annotations to AdCreative. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) abstract public class AdCreativeMixin extends FacebookObjectMixin { @JsonProperty("id") - private String id; + String id; @JsonProperty("object_type") - private AdCreative.AdCreativeType type; + AdCreative.AdCreativeType type; @JsonProperty("name") - private String name; + String name; @JsonProperty("title") - private String title; + String title; @JsonProperty("run_status") - private AdCreative.AdCreativeStatus status; + AdCreative.AdCreativeStatus status; @JsonProperty("body") - private String body; + String body; @JsonProperty("object_id") - private String objectId; + String objectId; @JsonProperty("image_hash") - private String imageHash; + String imageHash; @JsonProperty("image_url") - private String imageUrl; + String imageUrl; @JsonProperty("link_url") - private String linkUrl; + String linkUrl; @JsonProperty("object_story_id") - private String objectStoryId; + String objectStoryId; @JsonProperty("object_url") - private String objectUrl; + String objectUrl; @JsonProperty("url_tags") - private String urlTags; + String urlTags; @JsonProperty("thumbnail_url") - private String thumbnailUrl; - + String thumbnailUrl; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightActionMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightActionMixin.java index c97829000..f0215ab0d 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightActionMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightActionMixin.java @@ -5,13 +5,15 @@ import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; /** + * Annotated mixin to add Jackson annotations to AdInsightAction. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class AdInsightActionMixin extends FacebookObjectMixin { - @JsonProperty("action_type") - private String actionType; + @JsonProperty("action_type") + String actionType; - @JsonProperty("value") - private double value; + @JsonProperty("value") + double value; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightMixin.java index 3ff59fc4c..ab9c633c9 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdInsightMixin.java @@ -10,151 +10,151 @@ /** * Annotated mixin to add Jackson annotations to AdInsight. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class AdInsightMixin extends FacebookObjectMixin { // id fields @JsonProperty("account_id") - private String accountId; + String accountId; @JsonProperty("adgroup_id") - private String adGroupId; + String adGroupId; @JsonProperty("campaign_id") - private String campaignId; + String campaignId; @JsonProperty("campaign_group_id") - private String campaignGroupId; + String campaignGroupId; // name fields @JsonProperty("account_name") - private String accountName; + String accountName; @JsonProperty("adgroup_name") - private String adGroupName; + String adGroupName; @JsonProperty("campaign_group_name") - private String campaignGroupName; + String campaignGroupName; @JsonProperty("campaign_name") - private String camapignName; + String camapignName; // date fields @JsonProperty("date_start") - private Date dateStart; + Date dateStart; @JsonProperty("date_stop") - private Date dateStop; + Date dateStop; @JsonProperty("campaign_start") - private Date campaignStart; + Date campaignStart; @JsonProperty("campaign_end") - private Date campaignEnd; + Date campaignEnd; @JsonProperty("campaign_group_end") - private Date campaignGroupEnd; + Date campaignGroupEnd; // general fields @JsonProperty("actions_per_impression") - private double actionsPerImpression; + double actionsPerImpression; @JsonProperty("clicks") - private int clicks; + int clicks; @JsonProperty("unique_clicks") - private int uniqueClicks; + int uniqueClicks; @JsonProperty("cost_per_result") - private double costPerResult; + double costPerResult; @JsonProperty("cost_per_total_action") - private double costPerTotalAction; + double costPerTotalAction; @JsonProperty("cpc") - private double costPerClick; + double costPerClick; @JsonProperty("cost_per_unique_click") - private double costPerUniqueClick; + double costPerUniqueClick; @JsonProperty("cpm") - private double cpm; + double cpm; @JsonProperty("cpp") - private double cpp; + double cpp; @JsonProperty("ctr") - private double ctr; + double ctr; @JsonProperty("unique_ctr") - private double uniqueCtr; + double uniqueCtr; @JsonProperty("frequency") - private double frequency; + double frequency; @JsonProperty("impressions") - private int impressions; + int impressions; @JsonProperty("unique_impressions") - private int uniqueImpressions; + int uniqueImpressions; @JsonProperty("objective") - private String objective; + String objective; @JsonProperty("reach") - private int reach; + int reach; @JsonProperty("result_rate") - private double resultRate; + double resultRate; @JsonProperty("results") - private int results; + int results; @JsonProperty("roas") - private int roas; + int roas; @JsonProperty("social_clicks") - private int socialClicks; + int socialClicks; @JsonProperty("unique_social_clicks") - private int uniqueSocialClicks; + int uniqueSocialClicks; @JsonProperty("social_impressions") - private int socialImpressions; + int socialImpressions; @JsonProperty("unique_social_impressions") - private int uniqueSocialImpressions; + int uniqueSocialImpressions; @JsonProperty("social_reach") - private int socialReach; + int socialReach; @JsonProperty("spend") - private int spend; + int spend; @JsonProperty("today_spend") - private int todaySpend; + int todaySpend; @JsonProperty("total_action_value") - private int totalActionValue; + int totalActionValue; @JsonProperty("total_actions") - private int totalActions; + int totalActions; @JsonProperty("total_unique_actions") - private int totalUniqueActions; + int totalUniqueActions; // action and video fields @JsonProperty("actions") - private List actions; + List actions; @JsonProperty("unique_actions") - private List uniqueActions; + List uniqueActions; @JsonProperty("cost_per_action_type") - private List costPerActionType; + List costPerActionType; @JsonProperty("video_start_actions") - private List videoStartActions; - -} + List videoStartActions; +} \ No newline at end of file diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java index 3b78921eb..19c3c24e3 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java @@ -2,16 +2,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.social.facebook.api.ads.Ad; import org.springframework.social.facebook.api.ads.BidInfo; import org.springframework.social.facebook.api.ads.BidType; @@ -21,50 +17,51 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** + * Annotated mixin to add Jackson annotations to Ad. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) public class AdMixin { @JsonProperty("id") - private String id; + String id; @JsonProperty("adgroup_status") - private Ad.AdStatus status; + Ad.AdStatus status; @JsonProperty("name") - private String name; + String name; @JsonProperty("bid_type") - private BidType bidType; + BidType bidType; @JsonProperty("bid_info") - private BidInfo bidInfo; + BidInfo bidInfo; @JsonProperty("account_id") - private String accountId; + String accountId; @JsonProperty("campaign_id") - private String adSetId; + String adSetId; @JsonProperty("campaign_group_id") - private String campaignId; + String campaignId; @JsonProperty("creative") @JsonDeserialize(using = CreativeIdDeserializer.class) - private String creativeId; + String creativeId; @JsonProperty("targeting") - private Targeting targeting; + Targeting targeting; @JsonProperty("created_time") - private Date createdTime; + Date createdTime; @JsonProperty("updated_time") - private Date updatedTime; + Date updatedTime; private static class CreativeIdDeserializer extends JsonDeserializer { @Override diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java index 92108ae43..b0607c406 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java @@ -2,100 +2,70 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; -import org.springframework.social.facebook.api.ads.BidType; import org.springframework.social.facebook.api.ads.BidInfo; +import org.springframework.social.facebook.api.ads.BidType; import org.springframework.social.facebook.api.ads.Targeting; import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; -import java.io.IOException; import java.util.Date; import java.util.List; /** - * * Annotated mixin to add Jackson annotations to AdSet. + * Annotated mixin to add Jackson annotations to AdSet. * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) abstract public class AdSetMixin extends FacebookObjectMixin { @JsonProperty("id") - private String id; + String id; @JsonProperty("account_id") - private String accountId; + String accountId; @JsonProperty("campaign_group_id") - private String campaignId; + String campaignId; @JsonProperty("name") - private String name; + String name; @JsonProperty("campaign_status") - @JsonDeserialize(using = AdSetStatusDeserializer.class) - private AdSetStatus status; + AdSetStatus status; @JsonProperty("is_autobid") - private boolean autobid; + boolean autobid; @JsonProperty("bid_info") - private BidInfo bidInfo; + BidInfo bidInfo; @JsonProperty("bid_type") - @JsonDeserialize(using = BidTypeDeserializer.class) - private BidType bidType; + BidType bidType; @JsonProperty("budget_remaining") - private int budgetRemaining; + int budgetRemaining; @JsonProperty("daily_budget") - private int dailyBudget; + int dailyBudget; @JsonProperty("lifetime_budget") - private int lifetimeBudget; + int lifetimeBudget; @JsonProperty("creative_sequence") - private List creativeSequence; + List creativeSequence; @JsonProperty("targeting") - private Targeting targeting; + Targeting targeting; @JsonProperty("start_time") - private Date startTime; + Date startTime; @JsonProperty("end_time") - private Date endTime; + Date endTime; @JsonProperty("created_time") - private Date createdTime; + Date createdTime; @JsonProperty("updated_time") - private Date updatedTime; - - private static class AdSetStatusDeserializer extends JsonDeserializer { - @Override - public AdSetStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - return AdSetStatus.valueOf(jp.getValueAsString().toUpperCase()); - } catch (IllegalArgumentException e) { - return AdSetStatus.UNKNOWN; - } - } - } - - private static class BidTypeDeserializer extends JsonDeserializer { - @Override - public BidType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - return BidType.valueOf(jp.getValueAsString().toUpperCase()); - } catch (IllegalArgumentException e) { - return BidType.UNKNOWN; - } - } - } + Date updatedTime; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdUserMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdUserMixin.java index 32b6b20d2..34007dff6 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdUserMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdUserMixin.java @@ -2,103 +2,28 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.springframework.social.facebook.api.ads.AdUser.AdUserPermission; import org.springframework.social.facebook.api.ads.AdUser.AdUserRole; import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** * Annotated mixin to add Jackson annotations to AdUser. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class AdUserMixin extends FacebookObjectMixin { @JsonProperty("id") - private String id; + String id; @JsonProperty("name") - private String name; + String name; @JsonProperty("permissions") - @JsonDeserialize(using = AdUserPermissionListDeserializer.class) - private List permissions; + List permissions; @JsonProperty("role") - @JsonDeserialize(using = AdUserRoleDeserializer.class) - private AdUserRole role; - - private static class AdUserRoleDeserializer extends JsonDeserializer { - @Override - public AdUserRole deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - try { - int status = jp.getIntValue(); - switch (status) { - case 1001: - return AdUserRole.ADMINISTRATOR; - case 1002: - return AdUserRole.ADVERTISER; - case 1003: - return AdUserRole.ANALYST; - case 1004: - return AdUserRole.SALES; - default: - return AdUserRole.UNKNOWN; - } - } catch (IOException e) { - return AdUserRole.UNKNOWN; - } - } - } - - private static class AdUserPermissionListDeserializer extends JsonDeserializer> { - @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - if (jp.getCurrentToken() == JsonToken.START_ARRAY) { - List permissions = new ArrayList(); - try { - while (jp.nextToken() != JsonToken.END_ARRAY) { - int permissionValue = jp.getIntValue(); - switch (permissionValue) { - case 1: - permissions.add(AdUserPermission.ACCOUNT_ADMIN); - break; - case 2: - permissions.add(AdUserPermission.ADMANAGER_READ); - break; - case 3: - permissions.add(AdUserPermission.ADMANAGER_WRITE); - break; - case 4: - permissions.add(AdUserPermission.BILLING_READ); - break; - case 5: - permissions.add(AdUserPermission.BILLING_WRITE); - break; - case 7: - permissions.add(AdUserPermission.REPORTS); - break; - default: - permissions.add(AdUserPermission.UNKNOWN); - break; - } - } - return permissions; - } catch (IOException e) { - return Collections.emptyList(); - } - } - return Collections.emptyList(); - } - } - + AdUserRole role; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java deleted file mode 100644 index 65623c77d..000000000 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/BidInfoMixin.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.springframework.social.facebook.api.ads.impl.json; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; - -/** - * @author Sebastian Górecki - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public abstract class BidInfoMixin extends FacebookObjectMixin { -} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java index 1cff1cd62..0587d5230 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingCityEntryMixin.java @@ -5,17 +5,18 @@ import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; /** + * Annotated mixin to add Jackson annotations to TargetingCityEntry. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class TargetingCityEntryMixin extends FacebookObjectMixin { @JsonProperty("key") - private String key; + String key; @JsonProperty("radius") - private int radius; + int radius; @JsonProperty("distance_unit") - private String distanceUnit; - + String distanceUnit; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java index 0fa1d43ec..cad5a8bb8 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingEntryMixin.java @@ -4,12 +4,14 @@ import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; /** + * Annotated mixin to add Jackson annotations to TargetingEntry. + * * @author Sebastian Górecki */ public abstract class TargetingEntryMixin extends FacebookObjectMixin { @JsonProperty("id") - private long id; + long id; @JsonProperty("name") - private String name; + String name; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java index 8f6648bbb..aa5f402e1 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java @@ -20,58 +20,41 @@ import java.util.List; /** + * Annotated mixin to add Jackson annotations to TargetingLocation. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) @JsonSerialize(using = TargetingLocationSerializer.class) public abstract class TargetingLocationMixin extends FacebookObjectMixin { @JsonProperty("countries") - private List countries; + List countries; @JsonProperty("regions") - @JsonDeserialize(using = RegionListDeserializer.class) - private List regions; + @JsonDeserialize(using = ListOfMapsDeserializer.class) + List regions; @JsonProperty("cities") - private List cities; + List cities; @JsonProperty("zips") - @JsonDeserialize(using = ZipListDeserializer.class) - private List zips; + @JsonDeserialize(using = ListOfMapsDeserializer.class) + List zips; @JsonProperty("location_types") - private List locationTypes; + List locationTypes; - private static class RegionListDeserializer extends JsonDeserializer> { + private static class ListOfMapsDeserializer extends JsonDeserializer> { @Override public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (jp.getCurrentToken() == JsonToken.START_ARRAY) { - List regions = new ArrayList(); + List retList = new ArrayList(); try { while (jp.nextToken() != JsonToken.END_ARRAY) { HashMap regionMap = jp.readValueAs(HashMap.class); - regions.add(regionMap.get("key")); + retList.add(regionMap.get("key")); } - return regions; - } catch (IOException e) { - return Collections.emptyList(); - } - } - return Collections.emptyList(); - } - } - - private static class ZipListDeserializer extends JsonDeserializer> { - @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - if (jp.getCurrentToken() == JsonToken.START_ARRAY) { - List zips = new ArrayList(); - try { - while (jp.nextToken() != JsonToken.END_ARRAY) { - HashMap zipMap = jp.readValueAs(HashMap.class); - zips.add(zipMap.get("key")); - } - return zips; + return retList; } catch (IOException e) { return Collections.emptyList(); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java index 90496192d..99b748146 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationSerializer.java @@ -5,8 +5,6 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.springframework.social.facebook.api.ads.TargetingLocation; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import java.io.IOException; import java.util.HashMap; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java index f6008f413..87cb8d08a 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingMixin.java @@ -2,28 +2,17 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.springframework.social.facebook.api.ads.Targeting; import org.springframework.social.facebook.api.ads.Targeting.*; import org.springframework.social.facebook.api.ads.TargetingEntry; import org.springframework.social.facebook.api.ads.TargetingLocation; import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** + * Annotated mixin to add Jackson annotations to Targeting. + * * @author Sebastian Górecki */ @JsonIgnoreProperties(ignoreUnknown = true) @@ -31,65 +20,65 @@ public abstract class TargetingMixin extends FacebookObjectMixin { // demographics @JsonProperty("genders") - private List genders; + List genders; @JsonProperty("age_min") - private Integer ageMin; + Integer ageMin; @JsonProperty("age_max") - private Integer ageMax; + Integer ageMax; @JsonProperty("relationship_statuses") - private List relationshipStatuses; + List relationshipStatuses; @JsonProperty("interested_in") - private List interestedIn; + List interestedIn; // location @JsonProperty("geo_locations") - private TargetingLocation geoLocations; + TargetingLocation geoLocations; @JsonProperty("excluded_geo_locations") - private TargetingLocation excludedGeoLocations; + TargetingLocation excludedGeoLocations; // placement @JsonProperty("page_types") - private List pageTypes; + List pageTypes; // connections @JsonProperty("connections") - private List connections; + List connections; @JsonProperty("excluded_connections") - private List excludedConnections; + List excludedConnections; @JsonProperty("friends_of_connections") - private List friendsOfConnections; + List friendsOfConnections; // interests @JsonProperty("interests") - private List interests; + List interests; // behaviors @JsonProperty("behaviors") - private List behaviors; + List behaviors; // education and workplace @JsonProperty("education_schools") - private List educationSchools; + List educationSchools; @JsonProperty("education_statuses") - private List educationStatuses; + List educationStatuses; @JsonProperty("college_years") - private List collegeYears; + List collegeYears; @JsonProperty("education_majors") - private List educationMajors; + List educationMajors; @JsonProperty("work_employers") - private List workEmployers; + List workEmployers; @JsonProperty("work_positions") - private List workPositions; + List workPositions; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java index 52bb57673..2907f264c 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/json/FacebookModule.java @@ -133,7 +133,6 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(AdAccount.class, AdAccountMixin.class); context.setMixInAnnotations(AdCampaign.class, AdCampaignMixin.class); - context.setMixInAnnotations(BidInfo.class, BidInfoMixin.class); context.setMixInAnnotations(Targeting.class, TargetingMixin.class); context.setMixInAnnotations(TargetingCityEntry.class, TargetingCityEntryMixin.class); context.setMixInAnnotations(TargetingEntry.class, TargetingEntryMixin.class); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java index a734a9c88..b86f10409 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AccountTemplateTest.java @@ -4,8 +4,7 @@ import org.springframework.http.MediaType; import org.springframework.social.NotAuthorizedException; import org.springframework.social.facebook.api.PagedList; -import org.springframework.social.facebook.api.ads.*; -import org.springframework.social.facebook.api.ads.AdAccount.Capabilities; +import org.springframework.social.facebook.api.ads.AdAccount.Capability; import org.springframework.social.facebook.api.ads.AdAccount.TaxStatus; import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; @@ -96,10 +95,10 @@ public void getAdAccount_withUnknownCapabilities() throws Exception { assertEquals("act_123456789", adAccount.getId()); assertEquals(123456789, adAccount.getAccountId()); assertEquals(4, adAccount.getCapabilities().size()); - assertEquals(Capabilities.UNKNOWN, adAccount.getCapabilities().get(0)); - assertEquals(Capabilities.UNKNOWN, adAccount.getCapabilities().get(1)); - assertEquals(Capabilities.UNKNOWN, adAccount.getCapabilities().get(2)); - assertEquals(Capabilities.PREMIUM, adAccount.getCapabilities().get(3)); + assertEquals(Capability.UNKNOWN, adAccount.getCapabilities().get(0)); + assertEquals(Capability.UNKNOWN, adAccount.getCapabilities().get(1)); + assertEquals(Capability.UNKNOWN, adAccount.getCapabilities().get(2)); + assertEquals(Capability.PREMIUM, adAccount.getCapabilities().get(3)); } @Test @@ -464,8 +463,8 @@ private void assertAdAccountsFields(List adAccounts) { assertEquals(null, adAccounts.get(1).getBusinessStreet2()); assertEquals("77-777", adAccounts.get(1).getBusinessZip()); assertEquals(2, adAccounts.get(1).getCapabilities().size()); - assertEquals(Capabilities.DIRECT_SALES, adAccounts.get(1).getCapabilities().get(0)); - assertEquals(Capabilities.VIEW_TAGS, adAccounts.get(1).getCapabilities().get(1)); + assertEquals(Capability.DIRECT_SALES, adAccounts.get(1).getCapabilities().get(0)); + assertEquals(Capability.VIEW_TAGS, adAccounts.get(1).getCapabilities().get(1)); assertEquals(toDate("2015-04-20T00:31:33+0100"), adAccounts.get(1).getCreatedTime()); assertEquals("PLN", adAccounts.get(1).getCurrency()); assertEquals("77", adAccounts.get(1).getDailySpendLimit()); @@ -502,8 +501,8 @@ private void assertAdAccountFields(AdAccount adAccount) { assertEquals(null, adAccount.getBusinessStreet2()); assertEquals("66-777", adAccount.getBusinessZip()); assertEquals(2, adAccount.getCapabilities().size()); - assertEquals(Capabilities.DIRECT_SALES, adAccount.getCapabilities().get(0)); - assertEquals(Capabilities.VIEW_TAGS, adAccount.getCapabilities().get(1)); + assertEquals(Capability.DIRECT_SALES, adAccount.getCapabilities().get(0)); + assertEquals(Capability.VIEW_TAGS, adAccount.getCapabilities().get(1)); assertEquals(toDate("2015-02-19T00:31:33+0100"), adAccount.getCreatedTime()); assertEquals("PLN", adAccount.getCurrency()); assertEquals("93263", adAccount.getDailySpendLimit()); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index 622ed4def..b9bffee63 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -130,6 +130,37 @@ public void getAdSet() throws Exception { assertEquals(toDate("2015-04-10T13:32:09+0200"), adSet.getUpdatedTime()); } + @Test + public void getAdSet_wrongStatus() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/709123456789?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-set-wrong-status"), MediaType.APPLICATION_JSON)); + AdSet adSet = facebookAds.adSetOperations().getAdSet("709123456789"); + assertEquals("709123456789", adSet.getId()); + assertEquals("123456789", adSet.getAccountId()); + assertEquals("600123456789", adSet.getCampaignId()); + assertEquals("Test AdSet", adSet.getName()); + assertEquals(AdSetStatus.UNKNOWN, adSet.getStatus()); + mockServer.verify(); + } + + @Test + public void getAdSet_wrongBidType() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/710123456789?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-set-wrong-bid-type"), MediaType.APPLICATION_JSON)); + AdSet adSet = facebookAds.adSetOperations().getAdSet("710123456789"); + assertEquals("710123456789", adSet.getId()); + assertEquals("123456789", adSet.getAccountId()); + assertEquals("600123456789", adSet.getCampaignId()); + assertEquals("Test AdSet", adSet.getName()); + assertEquals(AdSetStatus.ACTIVE, adSet.getStatus()); + assertEquals(BidType.UNKNOWN, adSet.getBidType()); + mockServer.verify(); + } + @Test(expected = NotAuthorizedException.class) public void getAdSet_unauthorized() throws Exception { unauthorizedFacebookAds.adSetOperations().getAdSet("700123456789"); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java index bc751cc80..dc01cccd9 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java @@ -106,6 +106,7 @@ public void getAd() throws Exception { Ad ad = facebookAds.adOperations().getAd("100123456789"); verifyAd(ad); assertEquals(Ad.AdStatus.ACTIVE, ad.getStatus()); + assertEquals(BidType.ABSOLUTE_OCPM, ad.getBidType()); mockServer.verify(); } @@ -119,6 +120,21 @@ public void getAd_withWrongStatus() throws Exception { Ad ad = facebookAds.adOperations().getAd("100123456789"); verifyAd(ad); assertEquals(Ad.AdStatus.UNKNOWN, ad.getStatus()); + assertEquals(BidType.ABSOLUTE_OCPM, ad.getBidType()); + mockServer.verify(); + } + + @Test + public void getAd_withWrongBidType() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/100123456789?fields=id%2Caccount_id%2Cadgroup_status%2Cbid_type%2Cbid_info%2Ccampaign_id%2Ccampaign_group_id%2Ccreated_time%2Ccreative%2Cname%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-wrong-bid-type"), MediaType.APPLICATION_JSON)); + + Ad ad = facebookAds.adOperations().getAd("100123456789"); + verifyAd(ad); + assertEquals(Ad.AdStatus.ACTIVE, ad.getStatus()); + assertEquals(BidType.UNKNOWN, ad.getBidType()); mockServer.verify(); } @@ -293,7 +309,6 @@ public void deleteAd_unauthorized() throws Exception { private void verifyAd(Ad ad) { assertEquals("100123456789", ad.getId()); assertEquals("123456789", ad.getAccountId()); - assertEquals(BidType.ABSOLUTE_OCPM, ad.getBidType()); assertEquals("800123456789", ad.getAdSetId()); assertEquals("700123456789", ad.getCampaignId()); assertEquals(toDate("2015-04-10T09:28:54+0200"), ad.getCreatedTime()); diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index a5f7b4838..e7d04aa31 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -10,9 +10,7 @@ import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignStatus; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; @@ -91,7 +89,25 @@ public void getCampaign_withUnknownEnums() throws Exception { assertEquals(CampaignStatus.UNKNOWN, campaign.getStatus()); assertEquals("The test campaign name", campaign.getName()); assertEquals(CampaignObjective.UNKNOWN, campaign.getObjective()); + mockServer.verify(); + } + + @Test + public void getCampaign_withEmptyBuyingType() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/609123456789?fields=id%2Caccount_id%2Cbuying_type%2Ccampaign_group_status%2Cname%2Cobjective%2Cspend_cap")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-campaign-empty-buying-type"), MediaType.APPLICATION_JSON)); + AdCampaign campaign = facebookAds.campaignOperations().getAdCampaign("609123456789"); + assertEquals("609123456789", campaign.getId()); + assertEquals("123456789", campaign.getAccountId()); + assertNull(campaign.getBuyingType()); + assertEquals(CampaignStatus.ACTIVE, campaign.getStatus()); + assertEquals("The test campaign name", campaign.getName()); + assertEquals(CampaignObjective.POST_ENGAGEMENT, campaign.getObjective()); + assertEquals(1000, campaign.getSpendCap()); + mockServer.verify(); } @Test(expected = NotAuthorizedException.class) diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-empty-buying-type.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-empty-buying-type.json new file mode 100644 index 000000000..221b3d224 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-campaign-empty-buying-type.json @@ -0,0 +1,8 @@ +{ + "id": "609123456789", + "account_id": "123456789", + "campaign_group_status": "ACTIVE", + "name": "The test campaign name", + "objective": "POST_ENGAGEMENT", + "spend_cap": 1000 +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-bid-type.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-bid-type.json new file mode 100644 index 000000000..372393f63 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-bid-type.json @@ -0,0 +1,23 @@ +{ + "id": "710123456789", + "account_id": "123456789", + "campaign_group_id": "600123456789", + "name": "Test AdSet", + "campaign_status": "ACTIVE", + "is_autobid": true, + "bid_type": "WRONG_BID_TYPE", + "budget_remaining": 50, + "daily_budget": 0, + "lifetime_budget": 200, + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "start_time": "2015-04-12T09:19:00+0200", + "end_time": "2015-04-13T09:19:00+0200", + "created_time": "2015-04-10T09:28:54+0200", + "updated_time": "2015-04-10T13:32:09+0200" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-status.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-status.json new file mode 100644 index 000000000..44a73105c --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-wrong-status.json @@ -0,0 +1,23 @@ +{ + "id": "709123456789", + "account_id": "123456789", + "campaign_group_id": "600123456789", + "name": "Test AdSet", + "campaign_status": "WRONG_STATUS", + "is_autobid": true, + "bid_type": "ABSOLUTE_OCPM", + "budget_remaining": 50, + "daily_budget": 0, + "lifetime_budget": 200, + "targeting": { + "geo_locations": { + "countries": [ + "PL" + ] + } + }, + "start_time": "2015-04-12T09:19:00+0200", + "end_time": "2015-04-13T09:19:00+0200", + "created_time": "2015-04-10T09:28:54+0200", + "updated_time": "2015-04-10T13:32:09+0200" +} \ No newline at end of file diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-bid-type.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-bid-type.json new file mode 100644 index 000000000..861789a50 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-wrong-bid-type.json @@ -0,0 +1,45 @@ +{ + "id": "100123456789", + "account_id": "123456789", + "adgroup_status": "ACTIVE", + "bid_type": "WRONG_BID_TYPE", + "campaign_id": "800123456789", + "campaign_group_id": "700123456789", + "created_time": "2015-04-10T09:28:54+0200", + "creative": { + "id": "900123456789" + }, + "name": "Test ad name", + "targeting": { + "age_max": 20, + "age_min": 18, + "behaviors": [ + { + "id": "6004854404172", + "name": "Technology late adopters" + } + ], + "genders": [ + 1 + ], + "geo_locations": { + "countries": [ + "PL" + ], + "location_types": [ + "home", + "recent" + ] + }, + "interests": [ + { + "id": "6003629266583", + "name": "Hard drives" + } + ], + "page_types": [ + "feed" + ] + }, + "updated_time": "2015-04-10T13:32:09+0200" +} \ No newline at end of file From bf279bc127ea7522a4e4e3ff8fd5c714a8fcb6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Mon, 6 Jul 2015 12:24:50 +0200 Subject: [PATCH 25/28] Cleaning up things. --- .../facebook/api/ads/CampaignOperations.java | 1 - .../social/facebook/api/ads/FacebookAds.java | 30 +++++++++++++++++++ .../InvalidCampaignStatusException.java | 4 ++- .../api/ads/impl/AccountTemplate.java | 6 +--- .../facebook/api/ads/impl/AdSetTemplate.java | 5 +--- .../api/ads/impl/CampaignTemplate.java | 6 +--- .../api/ads/impl/FacebookAdsTemplate.java | 6 ++-- .../facebook/api/ads/impl/json/AdMixin.java | 1 + .../ads/impl/json/TargetingLocationMixin.java | 1 + .../api/impl/FacebookErrorHandler.java | 2 +- .../facebook/api/ads/AdTemplateTest.java | 4 +-- .../api/ads/CampaignTemplateTest.java | 1 - 12 files changed, 44 insertions(+), 23 deletions(-) rename spring-social-facebook/src/main/java/org/springframework/social/facebook/api/{ => ads}/InvalidCampaignStatusException.java (72%) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java index acf739a1f..e2501eb32 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/CampaignOperations.java @@ -3,7 +3,6 @@ import org.springframework.social.ApiException; import org.springframework.social.InsufficientPermissionException; import org.springframework.social.MissingAuthorizationException; -import org.springframework.social.facebook.api.InvalidCampaignStatusException; import org.springframework.social.facebook.api.PagedList; /** diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java index 30848a6f0..ba3ff951f 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/FacebookAds.java @@ -9,8 +9,38 @@ * @author Sebastian Górecki */ public interface FacebookAds { + /** + * API for working with Facebook Ad account. + * + * @return {@link AccountOperations} + */ AccountOperations accountOperations(); + + /** + * API for working with Facebook Ad campaign. + * + * @return {@link CampaignOperations} + */ CampaignOperations campaignOperations(); + + /** + * API for working with Facebook AdSet. + * + * @return {@link AdSetOperations} + */ AdSetOperations adSetOperations(); + + /** + * API for working with Facebook creative. + * + * @return {@link CreativeOperations} + */ CreativeOperations creativeOperations(); + + /** + * API for working with Facebook ad. + * + * @return {@link AdOperations} + */ + AdOperations adOperations(); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidCampaignStatusException.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/InvalidCampaignStatusException.java similarity index 72% rename from spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidCampaignStatusException.java rename to spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/InvalidCampaignStatusException.java index e2136ddce..87548c1e7 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/InvalidCampaignStatusException.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/InvalidCampaignStatusException.java @@ -1,4 +1,6 @@ -package org.springframework.social.facebook.api; +package org.springframework.social.facebook.api.ads; + +import org.springframework.social.facebook.api.InvalidParameterException; /** * Exception is thrown when a new ad campaign is created with status other that ACTIVE or PAUSED. diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java index 0f522e5ba..31fc40f60 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AccountTemplate.java @@ -7,7 +7,6 @@ import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; /** * @author Sebastian Górecki @@ -16,12 +15,9 @@ public class AccountTemplate extends AbstractFacebookOperations implements Accou private final GraphApi graphApi; - private final RestTemplate restTemplate; - - public AccountTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean isAuthorizedForUser) { + public AccountTemplate(GraphApi graphApi, boolean isAuthorizedForUser) { super(isAuthorizedForUser); this.graphApi = graphApi; - this.restTemplate = restTemplate; } public PagedList getAdAccounts(String userId) { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java index 58f0d4d49..902228aca 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java @@ -10,20 +10,17 @@ import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; /** * @author Sebastian Górecki */ public class AdSetTemplate extends AbstractFacebookOperations implements AdSetOperations { private GraphApi graphApi; - private RestTemplate restTemplate; private ObjectMapper mapper; - public AdSetTemplate(GraphApi graphApi, RestTemplate restTemplate, ObjectMapper mapper, boolean authorized) { + public AdSetTemplate(GraphApi graphApi, ObjectMapper mapper, boolean authorized) { super(authorized); this.graphApi = graphApi; - this.restTemplate = restTemplate; this.mapper = mapper; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java index b93040468..6293bff12 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/CampaignTemplate.java @@ -7,7 +7,6 @@ import org.springframework.social.facebook.api.impl.AbstractFacebookOperations; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; /** * @author Sebastian Górecki @@ -15,13 +14,10 @@ public class CampaignTemplate extends AbstractFacebookOperations implements CampaignOperations { private final GraphApi graphApi; - private final RestTemplate restTemplate; - - public CampaignTemplate(GraphApi graphApi, RestTemplate restTemplate, boolean isAuthorizedForUser) { + public CampaignTemplate(GraphApi graphApi, boolean isAuthorizedForUser) { super(isAuthorizedForUser); this.graphApi = graphApi; - this.restTemplate = restTemplate; } public PagedList getAdCampaigns(String accountId) { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java index 14dc3cd98..df43cb05b 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/FacebookAdsTemplate.java @@ -47,9 +47,9 @@ public AdOperations adOperations() { } private void initSubApis() { - accountOperations = new AccountTemplate(this, getRestTemplate(), isAuthorized()); - campaignOperations = new CampaignTemplate(this, getRestTemplate(), isAuthorized()); - adSetOperations = new AdSetTemplate(this, getRestTemplate(), getJsonMessageConverter().getObjectMapper(), isAuthorized()); + accountOperations = new AccountTemplate(this, isAuthorized()); + campaignOperations = new CampaignTemplate(this, isAuthorized()); + adSetOperations = new AdSetTemplate(this, getJsonMessageConverter().getObjectMapper(), isAuthorized()); creativeOperations = new CreativeTemplate(this, getRestTemplate(), isAuthorized()); adOperations = new AdTemplate(this, getRestTemplate(), getJsonMessageConverter().getObjectMapper(), isAuthorized()); } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java index 19c3c24e3..4a0c589a7 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdMixin.java @@ -64,6 +64,7 @@ public class AdMixin { Date updatedTime; private static class CreativeIdDeserializer extends JsonDeserializer { + @SuppressWarnings("unchecked") @Override public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (jp.getCurrentToken() == JsonToken.START_OBJECT) { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java index aa5f402e1..a81dbafe9 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/TargetingLocationMixin.java @@ -45,6 +45,7 @@ public abstract class TargetingLocationMixin extends FacebookObjectMixin { List locationTypes; private static class ListOfMapsDeserializer extends JsonDeserializer> { + @SuppressWarnings("unchecked") @Override public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (jp.getCurrentToken() == JsonToken.START_ARRAY) { diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java index 66dd39d65..11889a175 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java @@ -36,7 +36,7 @@ import org.springframework.social.ServerException; import org.springframework.social.UncategorizedApiException; import org.springframework.social.facebook.api.FacebookError; -import org.springframework.social.facebook.api.InvalidCampaignStatusException; +import org.springframework.social.facebook.api.ads.InvalidCampaignStatusException; import org.springframework.web.client.DefaultResponseErrorHandler; import com.fasterxml.jackson.core.JsonFactory; diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java index dc01cccd9..d9f6a48f2 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java @@ -6,8 +6,8 @@ import org.springframework.social.NotAuthorizedException; import org.springframework.social.facebook.api.PagedList; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.springframework.http.HttpMethod.*; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java index e7d04aa31..1b301c51d 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/CampaignTemplateTest.java @@ -4,7 +4,6 @@ import org.springframework.http.MediaType; import org.springframework.social.NotAuthorizedException; -import org.springframework.social.facebook.api.InvalidCampaignStatusException; import org.springframework.social.facebook.api.PagedList; import org.springframework.social.facebook.api.ads.AdCampaign.BuyingType; import org.springframework.social.facebook.api.ads.AdCampaign.CampaignObjective; From 6877f767c27a17c80ea262b3f22938cd36811c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Tue, 7 Jul 2015 14:17:43 +0200 Subject: [PATCH 26/28] Adds support for promoted_object in AdSet class. --- .../social/facebook/api/ads/AdSet.java | 9 ++ .../social/facebook/api/ads/BidInfo.java | 2 +- .../facebook/api/ads/PromotedObject.java | 9 ++ .../facebook/api/ads/impl/AdSetTemplate.java | 7 ++ .../api/ads/impl/json/AdSetMixin.java | 4 + .../facebook/api/ads/AdSetTemplateTest.java | 90 ++++++++++++++----- .../facebook/api/ads/AdTemplateTest.java | 14 +-- .../api/ads/ad-set-promoted-object.json | 33 +++++++ 8 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/PromotedObject.java create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-promoted-object.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java index 420c382cc..bb046bb17 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/AdSet.java @@ -27,6 +27,7 @@ public class AdSet extends FacebookObject { private int lifetimeBudget; private List creativeSequence; + private PromotedObject promotedObject; private Targeting targeting; private Date startTime; @@ -114,6 +115,14 @@ public List getCreativeSequence() { return creativeSequence; } + public PromotedObject getPromotedObject() { + return promotedObject; + } + + public void setPromotedObject(PromotedObject promotedObject) { + this.promotedObject = promotedObject; + } + public Targeting getTargeting() { return targeting; } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java index f8d6f5698..d6f1ca73f 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/BidInfo.java @@ -5,5 +5,5 @@ /** * @author Sebastian Górecki */ -public class BidInfo extends HashMap { +public class BidInfo extends HashMap { } diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/PromotedObject.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/PromotedObject.java new file mode 100644 index 000000000..f9df1dbd4 --- /dev/null +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/PromotedObject.java @@ -0,0 +1,9 @@ +package org.springframework.social.facebook.api.ads; + +import java.util.HashMap; + +/** + * @author Sebastian Górecki + */ +public class PromotedObject extends HashMap { +} diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java index 902228aca..8d46e4846 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/AdSetTemplate.java @@ -87,6 +87,13 @@ private MultiValueMap mapCommonFields(AdSet adSet) { } data.set("daily_budget", String.valueOf(adSet.getDailyBudget())); data.set("lifetime_budget", String.valueOf(adSet.getLifetimeBudget())); + if (adSet.getPromotedObject() != null) { + try { + data.set("promoted_object", mapper.writeValueAsString(adSet.getPromotedObject())); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } if (adSet.getTargeting() != null) { try { data.set("targeting", mapper.writeValueAsString(adSet.getTargeting())); diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java index b0607c406..55681508c 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/ads/impl/json/AdSetMixin.java @@ -5,6 +5,7 @@ import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; import org.springframework.social.facebook.api.ads.BidInfo; import org.springframework.social.facebook.api.ads.BidType; +import org.springframework.social.facebook.api.ads.PromotedObject; import org.springframework.social.facebook.api.ads.Targeting; import org.springframework.social.facebook.api.impl.json.FacebookObjectMixin; @@ -54,6 +55,9 @@ abstract public class AdSetMixin extends FacebookObjectMixin { @JsonProperty("creative_sequence") List creativeSequence; + @JsonProperty("promoted_object") + PromotedObject promotedObject; + @JsonProperty("targeting") Targeting targeting; diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java index b9bffee63..fcfd08c90 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdSetTemplateTest.java @@ -7,11 +7,11 @@ import org.springframework.social.facebook.api.ads.AdSet.AdSetStatus; import java.text.DateFormat; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; @@ -161,6 +161,31 @@ public void getAdSet_wrongBidType() throws Exception { mockServer.verify(); } + @Test + public void getAdSet_withPromotedObject() throws Exception { + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/705123456789?fields=account_id%2Cbid_info%2Cbid_type%2Cbudget_remaining%2Ccampaign_group_id%2Ccampaign_status%2Ccreated_time%2Ccreative_sequence%2Cdaily_budget%2Cend_time%2Cid%2Cis_autobid%2Clifetime_budget%2Cname%2Cpromoted_object%2Cstart_time%2Ctargeting%2Cupdated_time")) + .andExpect(method(GET)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withSuccess(jsonResource("ad-set-promoted-object"), MediaType.APPLICATION_JSON)); + AdSet adSet = facebookAds.adSetOperations().getAdSet("705123456789"); + assertEquals("705123456789", adSet.getId()); + assertEquals("123456789", adSet.getAccountId()); + assertEquals(Integer.valueOf(500), adSet.getBidInfo().get("CLICKS")); + assertEquals(BidType.ABSOLUTE_OCPM, adSet.getBidType()); + assertEquals(807, adSet.getBudgetRemaining()); + assertEquals("600123456789", adSet.getCampaignId()); + assertEquals(AdSetStatus.PAUSED, adSet.getStatus()); + assertEquals(2000, adSet.getDailyBudget()); + assertFalse(adSet.isAutobid()); + assertEquals(0, adSet.getLifetimeBudget()); + assertEquals("Test promoted object", adSet.getName()); + assertEquals("999888777666555", adSet.getPromotedObject().get("page_id")); + assertEquals(toDate("2015-07-06T14:18:55+0200"), adSet.getCreatedTime()); + assertEquals(toDate("2015-07-06T14:18:55+0200"), adSet.getStartTime()); + assertEquals(toDate("2015-07-06T14:18:55+0200"), adSet.getUpdatedTime()); + mockServer.verify(); + } + @Test(expected = NotAuthorizedException.class) public void getAdSet_unauthorized() throws Exception { unauthorizedFacebookAds.adSetOperations().getAdSet("700123456789"); @@ -252,20 +277,7 @@ public void createAdSet() throws Exception { .andExpect(header("Authorization", "OAuth someAccessToken")) .andExpect(content().string(requestBody)) .andRespond(withSuccess("{\"id\": \"701123456789\"}", MediaType.APPLICATION_JSON)); - AdSet adSet = new AdSet(); - adSet.setAutobid(true); - adSet.setBidType(BidType.ABSOLUTE_OCPM); - adSet.setCampaignId("600123456789"); - adSet.setStatus(AdSetStatus.PAUSED); - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - adSet.setEndTime(formatter.parse("2015-05-21 20:00:00")); - adSet.setName("Test AdSet"); - TargetingLocation location = new TargetingLocation(); - location.setCountries(Arrays.asList("PL")); - Targeting targeting = new Targeting(); - targeting.setGeoLocations(location); - adSet.setTargeting(targeting); - adSet.setLifetimeBudget(200); + AdSet adSet = createSampleAdSet(); assertEquals("701123456789", facebookAds.adSetOperations().createAdSet("123456789", adSet)); mockServer.verify(); @@ -274,7 +286,7 @@ public void createAdSet() throws Exception { @Test public void createAdSet_withAllFields() throws Exception { String requestBody = "date_format=U&name=Test+AdSet+2&campaign_status=ACTIVE&is_autobid=false&" + - "bid_info=%7B%22REACH%22%3A%221000%22%2C%22ACTIONS%22%3A%22200%22%2C%22SOCIAL%22%3A%22110%22%2C%22CLICKS%22%3A%22500%22%7D&" + + "bid_info=%7B%22REACH%22%3A1000%2C%22ACTIONS%22%3A200%2C%22SOCIAL%22%3A110%2C%22CLICKS%22%3A500%7D&" + "bid_type=ABSOLUTE_OCPM&daily_budget=4000&lifetime_budget=0&" + "targeting=%7B%22genders%22%3A%5B1%2C2%5D%2C%22age_min%22%3A45%2C%22age_max%22%3A55%2C%22relationship_statuses%22%3A%5B10%2C12%5D%2C%22interested_in%22%3A%5B1%2C2%5D%2C%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%2C%22DE%22%2C%22US%22%2C%22FR%22%5D%2C%22regions%22%3A%5B%7B%22key%22%3A%223847%22%7D%2C%7B%22key%22%3A%221111%22%7D%2C%7B%22key%22%3A%221234%22%7D%2C%7B%22key%22%3A%229888%22%7D%5D%2C%22cities%22%3A%5B%7B%22key%22%3A%222430536%22%2C%22radius%22%3A12%2C%22distance_unit%22%3A%22mile%22%7D%2C%7B%22key%22%3A%22777555%22%2C%22radius%22%3A1024%2C%22distance_unit%22%3A%22kilometer%22%7D%5D%2C%22zips%22%3A%5B%7B%22key%22%3A%22PL%3A62030%22%7D%2C%7B%22key%22%3A%22US%3A88123%22%7D%2C%7B%22key%22%3A%22FR%3A33144%22%7D%5D%2C%22location_types%22%3A%5B%22home%22%2C%22recent%22%5D%7D%2C%22excluded_geo_locations%22%3A%7B%22countries%22%3A%5B%22HU%22%2C%22JP%22%5D%2C%22regions%22%3A%5B%7B%22key%22%3A%221122%22%7D%2C%7B%22key%22%3A%2231415%22%7D%5D%2C%22cities%22%3A%5B%7B%22key%22%3A%2288997766%22%2C%22radius%22%3A12345%2C%22distance_unit%22%3A%22mile%22%7D%5D%2C%22zips%22%3A%5B%7B%22key%22%3A%22JP%3A44552%22%7D%5D%2C%22location_types%22%3A%5B%22home%22%5D%7D%2C%22page_types%22%3A%5B%22desktopfeed%22%2C%22mobilefeed-and-external%22%5D%2C%22connections%22%3A%5B%22123456789%22%2C%2255442211%22%5D%2C%22excluded_connections%22%3A%5B%2233441122%22%5D%2C%22friends_of_connections%22%3A%5B%22987654321%22%5D%2C%22interests%22%3A%5B%7B%22id%22%3A986123123123%2C%22name%22%3A%22Football%22%7D%5D%2C%22behaviors%22%3A%5B%7B%22id%22%3A1%2C%22name%22%3A%22Some+behavior%22%7D%5D%2C%22education_schools%22%3A%5B%7B%22id%22%3A10593123549%2C%22name%22%3A%22Poznan+University+of+Technology%22%7D%5D%2C%22education_statuses%22%3A%5B9%5D%2C%22college_years%22%3A%5B8%5D%2C%22education_majors%22%3A%5B%7B%22id%22%3A12%2C%22name%22%3A%22Some+major%22%7D%5D%2C%22work_employers%22%3A%5B%7B%22id%22%3A43125%2C%22name%22%3A%22Super+company%22%7D%5D%2C%22work_positions%22%3A%5B%7B%22id%22%3A11111%2C%22name%22%3A%22Developer%22%7D%5D%7D&" + "start_time=1432742400&end_time=1435420799&campaign_group_id=601123456789"; @@ -289,10 +301,10 @@ public void createAdSet_withAllFields() throws Exception { adSet.setStatus(AdSetStatus.ACTIVE); adSet.setAutobid(false); BidInfo bidInfo = new BidInfo(); - bidInfo.put("ACTIONS", "200"); - bidInfo.put("REACH", "1000"); - bidInfo.put("CLICKS", "500"); - bidInfo.put("SOCIAL", "110"); + bidInfo.put("ACTIONS", 200); + bidInfo.put("REACH", 1000); + bidInfo.put("CLICKS", 500); + bidInfo.put("SOCIAL", 110); adSet.setBidInfo(bidInfo); adSet.setBidType(BidType.ABSOLUTE_OCPM); adSet.setDailyBudget(4000); @@ -343,6 +355,24 @@ public void createAdSet_withAllFields() throws Exception { mockServer.verify(); } + @Test + public void createAdSet_withPromotedObject() throws Exception { + String requestBody = "date_format=U&name=Test+AdSet&campaign_status=PAUSED&is_autobid=true&bid_type=ABSOLUTE_OCPM&daily_budget=0&lifetime_budget=200&promoted_object=%7B%22page_id%22%3A%22111222333444555%22%7D&targeting=%7B%22geo_locations%22%3A%7B%22countries%22%3A%5B%22PL%22%5D%7D%7D&end_time=1432231200&campaign_group_id=600123456789"; + mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adcampaigns")) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andExpect(content().string(requestBody)) + .andRespond(withSuccess("{\"id\": \"702123456789\"}", MediaType.APPLICATION_JSON)); + + AdSet adSet = createSampleAdSet(); + PromotedObject promotedObject = new PromotedObject(); + promotedObject.put("page_id", "111222333444555"); + adSet.setPromotedObject(promotedObject); + + assertEquals("702123456789", facebookAds.adSetOperations().createAdSet("123456789", adSet)); + mockServer.verify(); + } + @Test(expected = NotAuthorizedException.class) public void createAdSet_unauthorized() throws Exception { unauthorizedFacebookAds.adSetOperations().createAdSet("123456789", new AdSet()); @@ -438,4 +468,22 @@ private void verifyAdSets(PagedList adSets) { assertEquals(Targeting.PageType.FEED, adSets.get(1).getTargeting().getPageTypes().get(0)); assertEquals(toDate("2015-04-10T13:32:09+0200"), adSets.get(1).getUpdatedTime()); } + + private AdSet createSampleAdSet() throws ParseException { + AdSet adSet = new AdSet(); + adSet.setAutobid(true); + adSet.setBidType(BidType.ABSOLUTE_OCPM); + adSet.setCampaignId("600123456789"); + adSet.setStatus(AdSetStatus.PAUSED); + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + adSet.setEndTime(formatter.parse("2015-05-21 20:00:00")); + adSet.setName("Test AdSet"); + TargetingLocation location = new TargetingLocation(); + location.setCountries(Arrays.asList("PL")); + Targeting targeting = new Targeting(); + targeting.setGeoLocations(location); + adSet.setTargeting(targeting); + adSet.setLifetimeBudget(200); + return adSet; + } } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java index d9f6a48f2..e97b62aa2 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ads/AdTemplateTest.java @@ -240,7 +240,7 @@ public void createAd() throws Exception { @Test public void createAd_withBidInfo() throws Exception { - String requestBody = "name=Test+ad&adgroup_status=PAUSED&bid_info=%7B%22REACH%22%3A%2211%22%2C%22ACTIONS%22%3A%2210%22%2C%22SOCIAL%22%3A%2250%22%2C%22CLICKS%22%3A%2212%22%7D&creative=%7B%22creative_id%22%3A+%22900123456789%22%7D&campaign_id=800123456789"; + String requestBody = "name=Test+ad&adgroup_status=PAUSED&bid_info=%7B%22REACH%22%3A11%2C%22ACTIONS%22%3A10%2C%22SOCIAL%22%3A50%2C%22CLICKS%22%3A12%7D&creative=%7B%22creative_id%22%3A+%22900123456789%22%7D&campaign_id=800123456789"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/act_123456789/adgroups")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) @@ -253,10 +253,10 @@ public void createAd_withBidInfo() throws Exception { ad.setAdSetId("800123456789"); ad.setCreativeId("900123456789"); BidInfo bidInfo = new BidInfo(); - bidInfo.put("ACTIONS", "10"); - bidInfo.put("REACH", "11"); - bidInfo.put("CLICKS", "12"); - bidInfo.put("SOCIAL", "50"); + bidInfo.put("ACTIONS", 10); + bidInfo.put("REACH", 11); + bidInfo.put("CLICKS", 12); + bidInfo.put("SOCIAL", 50); ad.setBidInfo(bidInfo); assertEquals("100123456789", facebookAds.adOperations().createAd("123456789", ad)); mockServer.verify(); @@ -269,7 +269,7 @@ public void createAd_unauthorized() throws Exception { @Test public void updateAd() throws Exception { - String requestBody = "name=Updated+Ad&adgroup_status=ARCHIVED&bid_info=%7B%22CLICKS%22%3A%22500%22%7D"; + String requestBody = "name=Updated+Ad&adgroup_status=ARCHIVED&bid_info=%7B%22CLICKS%22%3A500%7D"; mockServer.expect(requestTo("https://graph.facebook.com/v2.3/100123456789")) .andExpect(method(POST)) .andExpect(header("Authorization", "OAuth someAccessToken")) @@ -280,7 +280,7 @@ public void updateAd() throws Exception { ad.setStatus(Ad.AdStatus.ARCHIVED); ad.setName("Updated Ad"); BidInfo bidInfo = new BidInfo(); - bidInfo.put("CLICKS", "500"); + bidInfo.put("CLICKS", 500); ad.setBidInfo(bidInfo); assertTrue(facebookAds.adOperations().updateAd("100123456789", ad)); mockServer.verify(); diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-promoted-object.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-promoted-object.json new file mode 100644 index 000000000..41838d2ec --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/ads/ad-set-promoted-object.json @@ -0,0 +1,33 @@ +{ + "id": "705123456789", + "account_id": "123456789", + "bid_info": { + "CLICKS": 500 + }, + "bid_type": "ABSOLUTE_OCPM", + "budget_remaining": 807, + "campaign_group_id": "600123456789", + "campaign_status": "PAUSED", + "created_time": "2015-07-06T14:18:55+0200", + "daily_budget": 2000, + "is_autobid": false, + "lifetime_budget": 0, + "name": "Test promoted object", + "promoted_object": { + "page_id": "999888777666555" + }, + "start_time": "2015-07-06T14:18:55+0200", + "targeting": { + "age_max": 65, + "age_min": 18, + "geo_locations": { + "countries": [ + "PL" + ], + "location_types": [ + "home" + ] + } + }, + "updated_time": "2015-07-06T14:18:55+0200" +} \ No newline at end of file From f4a641131761897e5f1ad7ba5f49ffcc1101dd29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Wed, 5 Aug 2015 12:15:57 +0200 Subject: [PATCH 27/28] Adds support for InvalidParameterException. --- .../facebook/api/impl/FacebookErrorHandler.java | 3 +++ .../social/facebook/api/ErrorHandlingTest.java | 16 +++++++++++++--- .../facebook/api/error-100-invalidParameter.json | 11 +++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-100-invalidParameter.json diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java index 11889a175..c42393be5 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java @@ -36,6 +36,7 @@ import org.springframework.social.ServerException; import org.springframework.social.UncategorizedApiException; import org.springframework.social.facebook.api.FacebookError; +import org.springframework.social.facebook.api.InvalidParameterException; import org.springframework.social.facebook.api.ads.InvalidCampaignStatusException; import org.springframework.web.client.DefaultResponseErrorHandler; @@ -90,6 +91,8 @@ void handleFacebookError(HttpStatus statusCode, FacebookError error) { throw new ResourceNotFoundException(FACEBOOK_PROVIDER_ID, error.getMessage()); } else if (code == PARAM && error.getSubcode() != null && error.getSubcode() == 1487564) { throw new InvalidCampaignStatusException(FACEBOOK_PROVIDER_ID, error.getUserMessage()); + } else if (code == PARAM && error.getSubcode() != null) { + throw new InvalidParameterException(FACEBOOK_PROVIDER_ID, error.getUserMessage()); } else { throw new UncategorizedApiException(FACEBOOK_PROVIDER_ID, error.getMessage(), null); } diff --git a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ErrorHandlingTest.java b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ErrorHandlingTest.java index 2c5444bbc..2933a90b8 100644 --- a/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ErrorHandlingTest.java +++ b/spring-social-facebook/src/test/java/org/springframework/social/facebook/api/ErrorHandlingTest.java @@ -83,7 +83,18 @@ public void code100NonExistingFields() throws Exception { facebook.fetchObject("me", User.class); fail(); } - + + @Test(expected = InvalidParameterException.class) + public void code100InvalidParameter() throws Exception { + mockServer.expect(requestTo(fbUrl("me/feed"))) + .andExpect(method(POST)) + .andExpect(header("Authorization", "OAuth someAccessToken")) + .andRespond(withStatus(HttpStatus.BAD_REQUEST).body(jsonResource("error-100-invalidParameter")).contentType(MediaType.APPLICATION_JSON)); + facebook.feedOperations().post("me", ""); + fail(); + } + + @Test(expected=InvalidAuthorizationException.class) public void code104NoAccessToken() throws Exception { mockServer.expect(requestTo(fbUrl("me"))) @@ -182,6 +193,5 @@ public void code803UnknownPath() throws Exception { .andRespond(withStatus(HttpStatus.BAD_REQUEST).body(jsonResource("error-2500-bogusPath")).contentType(MediaType.APPLICATION_JSON)); facebook.fetchObject("me", User.class); fail(); - } - + } } diff --git a/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-100-invalidParameter.json b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-100-invalidParameter.json new file mode 100644 index 000000000..86480ae33 --- /dev/null +++ b/spring-social-facebook/src/test/resources/org/springframework/social/facebook/api/error-100-invalidParameter.json @@ -0,0 +1,11 @@ +{ + "error": { + "message": "Invalid parameter", + "type": "FacebookApiException", + "code": 100, + "error_subcode": 1349125, + "is_transient": false, + "error_user_title": "Missing Message Or Attachment", + "error_user_msg": "Missing message or attachment." + } +} \ No newline at end of file From ed61cddad3b2752d789915d571a5da992ef397a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B3recki?= Date: Wed, 5 Aug 2015 13:56:43 +0200 Subject: [PATCH 28/28] Adds two rate limit error codes. --- .../social/facebook/api/FacebookErrors.java | 8 +++++++- .../social/facebook/api/impl/FacebookErrorHandler.java | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookErrors.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookErrors.java index 3e69a2340..7736e4c86 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookErrors.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/FacebookErrors.java @@ -194,7 +194,10 @@ public class FacebookErrors { public static final int POKE_OUTSTANDING = 511; public static final int POKE_RATE = 512; public static final int POKE_USER_BLOCKED = 513; - + + // Rate limiting errors + public static final int USER_APP_TOO_MANY_CALLS = 613; + // Ref errors public static final int REF_SET_FAILED = 700; @@ -304,6 +307,9 @@ public class FacebookErrors { public static final int TEST_ACCOUNTS_INVALID_ID = 2901; public static final int TEST_ACCOUNTS_CANT_REMOVE_APP = 2902; public static final int TEST_ACCOUNTS_CANT_DELETE = 2903; + + // Ad Level Rate Limit error + public static final int AD_CREATION_LIMIT_EXCEEDED = 1487225; public static boolean isGeneralError(int code) { return code < 100; diff --git a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java index c42393be5..a6963c056 100644 --- a/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java +++ b/spring-social-facebook/src/main/java/org/springframework/social/facebook/api/impl/FacebookErrorHandler.java @@ -73,7 +73,9 @@ void handleFacebookError(HttpStatus statusCode, FacebookError error) { throw new UncategorizedApiException(FACEBOOK_PROVIDER_ID, error.getMessage(), null); } else if (code == SERVICE) { throw new ServerException(FACEBOOK_PROVIDER_ID, error.getMessage()); - } else if (code == TOO_MANY_CALLS || code == USER_TOO_MANY_CALLS || code == EDIT_FEED_TOO_MANY_USER_CALLS || code == EDIT_FEED_TOO_MANY_USER_ACTION_CALLS) { + } else if (code == TOO_MANY_CALLS || code == USER_TOO_MANY_CALLS || code == EDIT_FEED_TOO_MANY_USER_CALLS || + code == EDIT_FEED_TOO_MANY_USER_ACTION_CALLS || code == USER_APP_TOO_MANY_CALLS || + code == AD_CREATION_LIMIT_EXCEEDED) { throw new RateLimitExceededException(FACEBOOK_PROVIDER_ID); } else if (code == PERMISSION_DENIED || isUserPermissionError(code)) { throw new InsufficientPermissionException(FACEBOOK_PROVIDER_ID);