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