Skip to content

Commit 23bf94a

Browse files
authored
Manage audience upload by file (#573)
* Support create/update audience group by file API. * manage audience by file.
1 parent 3873d17 commit 23bf94a

File tree

14 files changed

+884
-1
lines changed

14 files changed

+884
-1
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2020 LINE Corporation
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.linecorp.bot.client;
18+
19+
import java.io.File;
20+
import java.util.concurrent.CompletableFuture;
21+
22+
import com.linecorp.bot.model.manageaudience.response.CreateAudienceForUploadingResponse;
23+
import com.linecorp.bot.model.response.BotApiResponse;
24+
25+
public interface ManageAudienceBlobClient {
26+
27+
/**
28+
* Create audience for uploading user IDs (by file).
29+
*/
30+
CompletableFuture<CreateAudienceForUploadingResponse> createAudienceForUploadingUserIds(
31+
String description,
32+
boolean isIfaAudience,
33+
String uploadDescription,
34+
File file
35+
);
36+
37+
/**
38+
* Add user IDs or Identifiers for Advertisers (IFAs) to an audience for uploading user IDs (by file).
39+
*/
40+
CompletableFuture<BotApiResponse> addUserIdsToAudience(
41+
long audienceGroupId,
42+
String uploadDescription,
43+
File file
44+
);
45+
46+
static ManageAudienceBlobClientBuilder builder() {
47+
return new ManageAudienceBlobClientBuilder();
48+
}
49+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* Copyright 2020 LINE Corporation
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.linecorp.bot.client;
18+
19+
import static java.util.Objects.requireNonNull;
20+
21+
import java.net.URI;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.concurrent.TimeUnit;
25+
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
import com.fasterxml.jackson.databind.ObjectMapper;
30+
31+
import com.linecorp.bot.model.objectmapper.ModelObjectMapper;
32+
33+
import lombok.NonNull;
34+
import lombok.Setter;
35+
import lombok.ToString;
36+
import lombok.experimental.Accessors;
37+
import lombok.experimental.PackagePrivate;
38+
import okhttp3.Interceptor;
39+
import okhttp3.OkHttpClient;
40+
import okhttp3.logging.HttpLoggingInterceptor;
41+
import okhttp3.logging.HttpLoggingInterceptor.Level;
42+
import retrofit2.Retrofit;
43+
import retrofit2.converter.jackson.JacksonConverterFactory;
44+
45+
@ToString
46+
@Accessors(fluent = true)
47+
public class ManageAudienceBlobClientBuilder {
48+
private static final ObjectMapper objectMapper = ModelObjectMapper.createNewObjectMapper();
49+
50+
/**
51+
* Use {@link ManageAudienceBlobClient#builder()} to create instance.
52+
*/
53+
@PackagePrivate
54+
ManageAudienceBlobClientBuilder() {
55+
}
56+
57+
/**
58+
* API Endpoint.
59+
*
60+
* <p>Default value = "https://api-data.line.me/".
61+
*/
62+
private URI apiEndPoint = LineClientConstants.DEFAULT_BLOB_END_POINT;
63+
64+
/**
65+
* API Endpoint.
66+
*
67+
* <p>Default value = "https://api.line.me/".
68+
*/ // We can remove this after delete `setApiEndPoint(String apiEndPoint)`.
69+
public ManageAudienceBlobClientBuilder apiEndPoint(URI apiEndPoint) {
70+
this.apiEndPoint = requireNonNull(apiEndPoint, "apiEndPoint");
71+
return this;
72+
}
73+
74+
/**
75+
* Connection timeout.
76+
*
77+
* <p>Default value = {@value LineClientConstants#DEFAULT_CONNECT_TIMEOUT_MILLIS}ms.
78+
*/
79+
@Setter
80+
private long connectTimeout = LineClientConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS;
81+
82+
/**
83+
* Connection timeout.
84+
*
85+
* <p>Default value = {@value LineClientConstants#DEFAULT_READ_TIMEOUT_MILLIS}ms.
86+
*/
87+
@Setter
88+
private long readTimeout = LineClientConstants.DEFAULT_READ_TIMEOUT_MILLIS;
89+
90+
/**
91+
* Write timeout.
92+
*
93+
* <p>Default value = {@value LineClientConstants#DEFAULT_WRITE_TIMEOUT_MILLIS}ms.
94+
*/
95+
@Setter
96+
private long writeTimeout = LineClientConstants.DEFAULT_WRITE_TIMEOUT_MILLIS;
97+
98+
/**
99+
* Channel token supplier of this client.
100+
*
101+
* <p>MUST BE NULL except you configured your own
102+
*/
103+
@Setter
104+
private ChannelTokenSupplier channelTokenSupplier;
105+
106+
/**
107+
* Custom {@link Retrofit.Builder} used internally.
108+
*
109+
* <p>If you want to use your own setting, specify {@link Retrofit.Builder} instance.
110+
* Default builder is used in case of {@code null} (default).
111+
*
112+
* <p>To use this method, please add dependency to 'com.squareup.retrofit2:retrofit'.
113+
*
114+
* @see #createDefaultRetrofitBuilder()
115+
*/
116+
@Setter
117+
private Retrofit.Builder retrofitBuilder;
118+
119+
/**
120+
* Add authentication header.
121+
*
122+
* <p>Default = {@value}. If you manage authentication header yourself, set to {@code false}.
123+
*/
124+
@Setter
125+
private boolean addAuthenticationHeader = true;
126+
127+
private OkHttpClient.Builder okHttpClientBuilder;
128+
129+
/**
130+
* Custom interceptors.
131+
*
132+
* <p>You can add your own interceptors.
133+
*
134+
* <p>Note: Authentication interceptor is automatically added by default.
135+
*
136+
* @see #addAuthenticationHeader(boolean)
137+
*/
138+
@Setter
139+
private List<Interceptor> additionalInterceptors = new ArrayList<>();
140+
141+
/**
142+
* Set fixed channel token. This overwrites {@link #channelTokenSupplier(ChannelTokenSupplier)}.
143+
*
144+
* @see #channelTokenSupplier(ChannelTokenSupplier)
145+
*/
146+
public ManageAudienceBlobClientBuilder channelToken(String channelToken) {
147+
channelTokenSupplier(FixedChannelTokenSupplier.of(channelToken));
148+
return this;
149+
}
150+
151+
/**
152+
* Set customized OkHttpClient.Builder.
153+
*
154+
* <p>In case of you need your own customized {@link OkHttpClient},
155+
* this builder allows specify {@link OkHttpClient.Builder} instance.
156+
*
157+
* <p>To use this method, please add dependency to 'com.squareup.retrofit2:retrofit'.
158+
*
159+
* @param addAuthenticationHeader If true, all default okhttp interceptors ignored.
160+
* You should insert authentication headers yourself.
161+
*/
162+
public ManageAudienceBlobClientBuilder okHttpClientBuilder(
163+
final @NonNull OkHttpClient.Builder okHttpClientBuilder,
164+
final boolean addAuthenticationHeader) {
165+
this.okHttpClientBuilder = okHttpClientBuilder;
166+
this.addAuthenticationHeader = addAuthenticationHeader;
167+
168+
return this;
169+
}
170+
171+
/**
172+
* Creates a new {@link ManageAudienceBlobService}.
173+
*/
174+
<T> T buildRetrofitIface(URI apiEndPoint, Class<T> retrofitIFace) {
175+
if (okHttpClientBuilder == null) {
176+
okHttpClientBuilder = new OkHttpClient.Builder();
177+
}
178+
179+
// Add interceptors.
180+
if (addAuthenticationHeader) {
181+
okHttpClientBuilder.addInterceptor(buildAuthenticationInterceptor(channelTokenSupplier));
182+
}
183+
if (additionalInterceptors != null) {
184+
additionalInterceptors.forEach(okHttpClientBuilder::addInterceptor);
185+
}
186+
okHttpClientBuilder.addInterceptor(buildLoggingInterceptor());
187+
188+
// Set timeout.
189+
okHttpClientBuilder
190+
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
191+
.readTimeout(readTimeout, TimeUnit.MILLISECONDS)
192+
.writeTimeout(writeTimeout, TimeUnit.MILLISECONDS);
193+
194+
final OkHttpClient okHttpClient = okHttpClientBuilder.build();
195+
196+
if (retrofitBuilder == null) {
197+
retrofitBuilder = createDefaultRetrofitBuilder();
198+
}
199+
retrofitBuilder.client(okHttpClient);
200+
retrofitBuilder.baseUrl(apiEndPoint.toString());
201+
202+
final Retrofit retrofit = retrofitBuilder.build();
203+
204+
return retrofit.create(retrofitIFace);
205+
}
206+
207+
static HeaderInterceptor buildAuthenticationInterceptor(ChannelTokenSupplier channelTokenSupplier) {
208+
requireNonNull(channelTokenSupplier, "channelTokenSupplier");
209+
return HeaderInterceptor.forChannelTokenSupplier(channelTokenSupplier);
210+
}
211+
212+
static Interceptor buildLoggingInterceptor() {
213+
final Logger slf4jLogger = LoggerFactory.getLogger("com.linecorp.bot.client.wire");
214+
215+
return new HttpLoggingInterceptor(slf4jLogger::info)
216+
.setLevel(Level.BODY);
217+
}
218+
219+
static Retrofit.Builder createDefaultRetrofitBuilder() {
220+
return new Retrofit.Builder()
221+
.addConverterFactory(JacksonConverterFactory.create(objectMapper));
222+
}
223+
224+
/**
225+
* Creates a new {@link ManageAudienceBlobService}.
226+
*/
227+
public ManageAudienceBlobClient build() {
228+
return new ManageAudienceBlobClientImpl(
229+
buildRetrofitIface(apiEndPoint, ManageAudienceBlobService.class));
230+
}
231+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2020 LINE Corporation
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.linecorp.bot.client;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.util.concurrent.CompletableFuture;
22+
23+
import com.fasterxml.jackson.databind.ObjectMapper;
24+
25+
import com.linecorp.bot.model.manageaudience.ManageAudienceException;
26+
import com.linecorp.bot.model.manageaudience.response.CreateAudienceForUploadingResponse;
27+
import com.linecorp.bot.model.objectmapper.ModelObjectMapper;
28+
import com.linecorp.bot.model.response.BotApiResponse;
29+
30+
import okhttp3.MediaType;
31+
import okhttp3.MultipartBody;
32+
import okhttp3.MultipartBody.Builder;
33+
import okhttp3.RequestBody;
34+
import retrofit2.Call;
35+
import retrofit2.Callback;
36+
import retrofit2.Response;
37+
38+
public class ManageAudienceBlobClientImpl implements ManageAudienceBlobClient {
39+
private static final ObjectMapper objectMapper = ModelObjectMapper.createNewObjectMapper();
40+
private final ManageAudienceBlobService retrofitImpl;
41+
42+
public ManageAudienceBlobClientImpl(ManageAudienceBlobService retrofitImpl) {
43+
this.retrofitImpl = retrofitImpl;
44+
}
45+
46+
@Override
47+
public CompletableFuture<CreateAudienceForUploadingResponse> createAudienceForUploadingUserIds(
48+
String description, boolean isIfaAudience, String uploadDescription, File file) {
49+
MultipartBody parts = new MultipartBody.Builder()
50+
.addFormDataPart("description", description)
51+
.addFormDataPart("isIfaAudience", String.valueOf(isIfaAudience))
52+
.addFormDataPart("uploadDescription", uploadDescription)
53+
.addFormDataPart("file", file.getName(),
54+
RequestBody.create(MediaType.get("text/plain"), file))
55+
.build();
56+
57+
return toFuture(retrofitImpl.createAudienceForUploadingUserIds(parts));
58+
}
59+
60+
@Override
61+
public CompletableFuture<BotApiResponse> addUserIdsToAudience(long audienceGroupId,
62+
String uploadDescription,
63+
File file) {
64+
MultipartBody parts = new Builder()
65+
.addFormDataPart("audienceGroupId", String.valueOf(audienceGroupId))
66+
.addFormDataPart("uploadDescription", uploadDescription)
67+
.addFormDataPart("file", file.getName(),
68+
RequestBody.create(MediaType.get("text/plain"), file))
69+
.build();
70+
71+
return LineMessagingClientImpl.toBotApiFuture(retrofitImpl.addUserIdsToAudience(
72+
parts
73+
));
74+
}
75+
76+
private static <T> CompletableFuture<T> toFuture(Call<T> call) {
77+
final CallbackCompletableFuture<T> future = new CallbackCompletableFuture<>();
78+
call.enqueue(future);
79+
return future;
80+
}
81+
82+
private static class CallbackCompletableFuture<T> extends CompletableFuture<T> implements Callback<T> {
83+
@Override
84+
public void onResponse(final Call<T> call, final Response<T> response) {
85+
if (response.isSuccessful()) {
86+
complete(response.body());
87+
return;
88+
}
89+
if (response.code() == 400) {
90+
try {
91+
completeExceptionally(objectMapper.readValue(response.errorBody().string(),
92+
ManageAudienceException.class));
93+
return;
94+
} catch (IOException e) {
95+
completeExceptionally(e);
96+
}
97+
}
98+
completeExceptionally(new ManageAudienceException(response.message()));
99+
}
100+
101+
@Override
102+
public void onFailure(final Call<T> call, final Throwable t) {
103+
completeExceptionally(new ManageAudienceException(t.getMessage(), t));
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)