<KeywordIds xmlns:a="http://schemas.datacontract.org/2004/07/System" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:long>15126706305</a:long>
<a:long i:nil="true" />
<a:long>15126706306</a:long>
<a:long i:nil="true" />
</KeywordIds>
ERROR [pool-8-thread-1] (BeanPropertyTarget.java:135) - Could not convert org.apache.axis.encoding.ser.ArrayDeserializer$ArrayListExtension to bean field 'keywordIds', type [J
ERROR [pool-8-thread-1] (Call.java:2469) - Exception:
java.lang.IllegalArgumentException
at org.apache.axis.encoding.ser.BeanPropertyTarget.set(BeanPropertyTarget.java:157)
at org.apache.axis.encoding.DeserializerImpl.valueComplete(DeserializerImpl.java:249)
Changing from long[] to Long[] will fix this problem
2. Bulk download campaign data or performance data potentially has extra Error row for each of BulkDownloadEntity as documented here. which means the first column Type should precisely has following set of values, looks ugly! I think the error should be combined to it's above entity row.
<xs:simpleType name="BulkDownloadEntity">
<xs:list>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Campaigns" />
<xs:enumeration value="AdGroups" />
<xs:enumeration value="Ads" />
<xs:enumeration value="Keywords" />
<xs:enumeration value="CampaignNegativeKeywords" />
<xs:enumeration value="AdGroupNegativeKeywords" />
<xs:enumeration value="CampaignTargets" />
<xs:enumeration value="AdGroupTargets" />
<xs:enumeration value="CampaignNegativeSites" />
<xs:enumeration value="AdGroupNegativeSites" />
<xs:enumeration value="CampaignSiteLinksAdExtensions" />
<xs:enumeration value="CampaignProductAdExtensions" />
<xs:enumeration value="CampaignLocationAdExtensions" />
<xs:enumeration value="CampaignCallAdExtensions" />
<xs:enumeration value="AdGroupProductTargets" />
<xs:enumeration value="AdGroupSiteLinksAdExtensions" />
<xs:enumeration value="CampaignsError" />
<xs:enumeration value="AdGroupsError" />
<xs:enumeration value="AdsError" />
<xs:enumeration value="KeywordsError" />
<xs:enumeration value="CampaignNegativeKeywordsError" />
<xs:enumeration value="AdGroupNegativeKeywordsError" />
<xs:enumeration value="CampaignTargetsError" />
<xs:enumeration value="AdGroupTargetsError" />
<xs:enumeration value="CampaignNegativeSitesError" />
<xs:enumeration value="AdGroupNegativeSitesError" />
<xs:enumeration value="CampaignSiteLinksAdExtensionsError" />
<xs:enumeration value="CampaignProductAdExtensionsError" />
<xs:enumeration value="CampaignLocationAdExtensionsError" />
<xs:enumeration value="CampaignCallAdExtensionsError" />
<xs:enumeration value="AdGroupProductTargetsError" />
<xs:enumeration value="AdGroupSiteLinksAdExtensionsError" />
</xs:restriction>
</xs:simpleType>
</xs:list>
</xs:simpleType>
So the campaign data parser has to deal with the potential Error rows, take the Keyword for example:
@Override public List<BiddableKeyword> getBiddableKeywords(int batchSize) throws IOException { List<BiddableKeyword> keywords = new ArrayList<BiddableKeyword>(); if (batchSize > 0 && !passedTypes.contains(TYPE_KEYWORD)) { boolean foundType = false; BiddableKeyword keyword = null; while (currentRow != null) { String rowType = getRowType(currentRow); if (rowType.equals(TYPE_KEYWORD)) { foundType = true; if (keywords.size() == batchSize) break; // check size first keyword = getBiddableKeyword(currentRow); keywords.add(keyword); } else if (rowType.equals(TYPE_KEYWORD + ERROR_SUFFIX)) { // handle the separate keyword error row!!! Long keywordApiId = Long.valueOf(getCellValue(currentRow, COLUMN_ID)); if (keyword.getApiId().equals(keywordApiId)) { keyword.setDisapprovalReasons("Reason code: " + getCellValue(currentRow, COLUMN_EDITORIAL_REASON_CODE)); } } else if (foundType) { passedTypes.add(TYPE_KEYWORD); break; } currentRow = reader.readNext(); } } return keywords; } private BiddableKeyword getBiddableKeyword(String[] dataRow) { BiddableKeyword keyword = new BiddableKeyword(); keyword.setSearchEngine(SearchEngine.BING); keyword.setUserStatus(getCellValue(dataRow, COLUMN_STATUS).toUpperCase()); keyword.setApiId(getCellValue(dataRow, COLUMN_ID)); keyword.getAdGroup(true).setApiId(getCellValue(dataRow, COLUMN_PARENT_ID)); keyword.getAdGroup().getCampaign(true).setCampaignName(getCellValue(dataRow, COLUMN_CAMPAIGN)); keyword.getAdGroup().setAdGroupName(getCellValue(dataRow, COLUMN_AD_GROUP)); keyword.setKeywordText(getCellValue(dataRow, COLUMN_KEYWORD)); if (keyword.getUserStatus() != UserStatus.DELETED) { String matchType = getCellValue(dataRow, COLUMN_MATCH_TYPE); keyword.setMatchType(matchType == null ? null : matchType.toUpperCase()); keyword.setMaxCpc(getCellValue(dataRow, COLUMN_BID)); keyword.setDestinationUrl(getCellValue(dataRow, COLUMN_DESTINATION_URL)); keyword.setAdParam1(getCellValue(dataRow, COLUMN_PARAM1)); keyword.setAdParam2(getCellValue(dataRow, COLUMN_PARAM2)); keyword.setAdParam3(getCellValue(dataRow, COLUMN_PARAM3)); keyword.setApprovalStatus(getCellValue(dataRow, COLUMN_EDITORIAL_STATUS)); keyword.setDisapprovalReasons(getCellValue(dataRow, COLUMN_EDITORIAL_REASON_CODE)); keyword.setQualityScore(getCellValue(dataRow, COLUMN_QUALITY_SCORE)); keyword.identifyBMM(); // identify BMM match type! } return keyword; }
3. One more issue I found is also for the bulk download, when you deleted some negative keywords, the downloaded report will show these keywords as Deleted, BUT the Keyword column is empty, and I don’t know which one was deleted since the negative keyword has no Id, this is not helpful for the merge,
So to merge negative keywords, I have to treat them as a whole set, means you get all negative keywords for some campaigns or ad groups, and fully merge to db. the parser to get negative keywords looks like this:
@Override public List<NegativeKeyword> getCampaignNegativeKeywords(int batchSize) throws IOException { List<NegativeKeyword> negativeKeywords = new ArrayList<NegativeKeyword>(); if (batchSize > 0 && !passedTypes.contains(TYPE_CAMPAIGN_NEGATIVE_KEYWORD)) { boolean foundType = false; while (currentRow != null) { String rowType = getRowType(currentRow); if (rowType.equals(TYPE_CAMPAIGN_NEGATIVE_KEYWORD)) { foundType = true; if (negativeKeywords.size() >= batchSize) break; // check size first! long campaignId = Long.valueOf(getCellValue(currentRow, COLUMN_PARENT_ID)); negativeKeywords.addAll(getCampaignNegativeKeywords(campaignId)); continue; } else if (rowType.equals(TYPE_CAMPAIGN_NEGATIVE_KEYWORD + ERROR_SUFFIX)) { // skip error line! } else if (foundType) { passedTypes.add(TYPE_CAMPAIGN_NEGATIVE_KEYWORD); break; } currentRow = reader.readNext(); } } return negativeKeywords; } // just for this single campaign! private List<NegativeKeyword> getCampaignNegativeKeywords(long campaignId) throws IOException { List<NegativeKeyword> negativeKeywords = new ArrayList<NegativeKeyword>(); if (campaignId > 0) { while (currentRow != null) { String rowType = getRowType(currentRow); if (rowType.equals(TYPE_CAMPAIGN_NEGATIVE_KEYWORD)) { NegativeKeyword keyword = getCampaignNegativeKeyword(currentRow); if (campaignId == keyword.getAdGroup().getCampaign().getApiId()) { if (!(keyword.getUserStatus() == UserStatus.DELETED && keyword.getKeywordText() == null)) // The delete one doesn't have keyword text, NOT helpful for merge! negativeKeywords.add(keyword); } else { break; } } else if (rowType.equals(TYPE_CAMPAIGN_NEGATIVE_KEYWORD + ERROR_SUFFIX)) { // skip error line! } else { break; } currentRow = reader.readNext(); } } return negativeKeywords; } private NegativeKeyword getCampaignNegativeKeyword(String[] dataRow) { NegativeKeyword negativeKeyword = new NegativeKeyword(); negativeKeyword.setMapLevel(MapLevel.CAMPAIGN); negativeKeyword.setUserStatus(getCellValue(dataRow, COLUMN_STATUS).toUpperCase()); negativeKeyword.getAdGroup(true).getCampaign(true).setApiId(getCellValue(dataRow, COLUMN_PARENT_ID)); negativeKeyword.getAdGroup().getCampaign().setCampaignName(getCellValue(dataRow, COLUMN_CAMPAIGN)); negativeKeyword.setKeywordName(SearchEngine.BING, getCellValue(dataRow, COLUMN_KEYWORD)); return negativeKeyword; }