Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimanaev committed May 22, 2019
2 parents f7dda37 + 0c66b31 commit 8120ae1
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 43 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ dependency-reduced-pom.xml
*.ipr
*.iws
*.idea
*.log
*.log

# Mac OS

.DS_Store
56 changes: 34 additions & 22 deletions src/main/java/com/taboola/backstage/Backstage.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.taboola.backstage.internal.*;
import com.taboola.backstage.internal.config.CommunicationConfig;
import com.taboola.backstage.internal.config.SerializationConfig;
import com.taboola.backstage.internal.factories.BackstageEndpointsFactory;
import com.taboola.backstage.internal.factories.BackstageEndpointsRetrofitFactory;
import com.taboola.backstage.internal.BackstageInternalTools;
Expand All @@ -16,7 +17,7 @@
* {@code
* Backstage backstage = Backstage.builder().build();
* }
*</pre>
* </pre>
*
* Example : getting all {@link com.taboola.backstage.model.media.campaigns.Campaign Campaigns} that belong to my account id
* <pre>
Expand Down Expand Up @@ -135,6 +136,7 @@ public static class BackstageBuilder {
private static final String VERSION = "1.0.10";
private static final Integer DEFAULT_MAX_IDLE_CONNECTIONS = 5;
private static final Long DEFAULT_KEEP_ALIVE_DURATION_MILLIS = 300_000L;
private static final SerializationConfig DEFAULT_SERIALIZATION_CONFIG = new SerializationConfig();

private String baseUrl;
private String authBaseUrl;
Expand All @@ -147,6 +149,7 @@ public static class BackstageBuilder {
private Boolean performClientValidations;
private Boolean debug;
private Boolean organizeDynamicColumns;
private SerializationConfig serializationConfig;

public BackstageBuilder setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
Expand Down Expand Up @@ -203,70 +206,79 @@ public BackstageBuilder setOrganizeDynamicColumns(Boolean organizeDynamicColumns
return this;
}

public BackstageBuilder setSerializationConfig(SerializationConfig serializationConfig) {
this.serializationConfig = serializationConfig;
return this;
}

public Backstage build() {
organizeState();
String finalUserAgent = String.format("Backstage/%s (%s)", VERSION, userAgent);
CommunicationConfig config = new CommunicationConfig(baseUrl, authBaseUrl, connectionTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, maxIdleConnections,
keepAliveDurationMillis, finalUserAgent, debug);
CommunicationFactory communicator = new CommunicationFactory(config);
CommunicationFactory communicator = new CommunicationFactory(config, serializationConfig);
BackstageEndpointsFactory endpointsFactory = new BackstageEndpointsRetrofitFactory(communicator);
return new Backstage(
new BackstageInternalToolsImpl(endpointsFactory),
new CampaignsServiceImpl(performClientValidations, endpointsFactory.createEndpoint(BackstageCampaignsEndpoint.class)),
new AuthenticationServiceImpl(endpointsFactory.createAuthEndpoint(BackstageAuthenticationEndpoint.class)),
new UserServiceImpl(endpointsFactory.createEndpoint(BackstageAccountEndpoint.class)),
new CampaignItemsServiceImpl(performClientValidations, endpointsFactory.createEndpoint(BackstageCampaignItemsEndpoint.class)),
new DictionaryServiceImpl(endpointsFactory.createEndpoint(BackstageDictionaryEndpoint.class)),
new ReportsServiceImpl(endpointsFactory.createEndpoint(BackstageMediaReportsEndpoint.class), endpointsFactory.createEndpoint(BackstagePublisherReportsEndpoint.class), organizeDynamicColumns),
new AccountsServiceImpl(endpointsFactory.createEndpoint(BackstageAccountEndpoint.class)),
new CampaignPostalTargetingServiceImpl(performClientValidations, endpointsFactory.createEndpoint(BackstagePostalTargetingEndpoint.class))
new BackstageInternalToolsImpl(endpointsFactory),
new CampaignsServiceImpl(performClientValidations, endpointsFactory.createEndpoint(BackstageCampaignsEndpoint.class)),
new AuthenticationServiceImpl(endpointsFactory.createAuthEndpoint(BackstageAuthenticationEndpoint.class)),
new UserServiceImpl(endpointsFactory.createEndpoint(BackstageAccountEndpoint.class)),
new CampaignItemsServiceImpl(performClientValidations, endpointsFactory.createEndpoint(BackstageCampaignItemsEndpoint.class)),
new DictionaryServiceImpl(endpointsFactory.createEndpoint(BackstageDictionaryEndpoint.class)),
new ReportsServiceImpl(endpointsFactory.createEndpoint(BackstageMediaReportsEndpoint.class), endpointsFactory.createEndpoint(BackstagePublisherReportsEndpoint.class), organizeDynamicColumns),
new AccountsServiceImpl(endpointsFactory.createEndpoint(BackstageAccountEndpoint.class)),
new CampaignPostalTargetingServiceImpl(performClientValidations, endpointsFactory.createEndpoint(BackstagePostalTargetingEndpoint.class))
);
}

private void organizeState() {
if(baseUrl == null) {
if (baseUrl == null) {
baseUrl = DEFAULT_BACKSTAGE_HOST;
}

if(authBaseUrl == null) {
if (authBaseUrl == null) {
authBaseUrl = DEFAULT_AUTH_BACKSTAGE_HOST;
}

if(connectionTimeoutMillis == null) {
if (connectionTimeoutMillis == null) {
connectionTimeoutMillis = 0L;
}

if(readTimeoutMillis == null) {
if (readTimeoutMillis == null) {
readTimeoutMillis = 0L;
}

if(writeTimeoutMillis == null) {
if (writeTimeoutMillis == null) {
writeTimeoutMillis = 0L;
}

if(maxIdleConnections == null) {
if (maxIdleConnections == null) {
maxIdleConnections = DEFAULT_MAX_IDLE_CONNECTIONS;
}

if(keepAliveDurationMillis == null) {
if (keepAliveDurationMillis == null) {
keepAliveDurationMillis = DEFAULT_KEEP_ALIVE_DURATION_MILLIS;
}

if(userAgent == null) {
if (userAgent == null) {
userAgent = DEFAULT_USER_AGENT;
}

if(performClientValidations == null) {
if (performClientValidations == null) {
performClientValidations = true;
}

if(debug == null) {
if (debug == null) {
debug = false;
}

if(organizeDynamicColumns == null) {
if (organizeDynamicColumns == null) {
organizeDynamicColumns = true;
}

if (serializationConfig == null) {
serializationConfig = DEFAULT_SERIALIZATION_CONFIG;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package com.taboola.backstage.internal;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.taboola.backstage.internal.config.CommunicationConfig;
import com.taboola.backstage.internal.config.SerializationConfig;
import com.taboola.backstage.internal.interceptors.CommunicationInterceptor;
import com.taboola.backstage.internal.interceptors.UserAgentInterceptor;
import com.taboola.backstage.internal.serialization.SerializationMapperCreator;

import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;

import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

Expand All @@ -28,21 +27,12 @@ public final class CommunicationFactory {
private final Retrofit retrofit;
private final Retrofit authRetrofit;

public CommunicationFactory(CommunicationConfig config) {
this.objectMapper = createObjectMapper();

Retrofit.Builder retrofitBuilder = createRetrofitBuilder(config);

this.authRetrofit = retrofitBuilder.baseUrl(config.getAuthenticationBaseUrl()).build();
this.retrofit = retrofitBuilder.baseUrl(config.getBackstageBaseUrl()).build();
}
public CommunicationFactory(CommunicationConfig communicationConfig, SerializationConfig serializationConfig) {
this.objectMapper = SerializationMapperCreator.createObjectMapper(serializationConfig);
Retrofit.Builder retrofitBuilder = createRetrofitBuilder(communicationConfig);

private ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
this.authRetrofit = retrofitBuilder.baseUrl(communicationConfig.getAuthenticationBaseUrl()).build();
this.retrofit = retrofitBuilder.baseUrl(communicationConfig.getBackstageBaseUrl()).build();
}

private HttpLoggingInterceptor createLoggingInterceptor(CommunicationConfig config) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.taboola.backstage.internal.config;

import java.util.HashMap;
import java.util.Map;

public class SerializationConfig {
private Map<Class<?>, Class<?>> mixins;
private boolean shouldIgnoreAnySetterAnnotation;

public SerializationConfig() {
mixins = new HashMap<>();
shouldIgnoreAnySetterAnnotation = false;
}

public SerializationConfig setMixins(Map<Class<?>, Class<?>> mixins) {
this.mixins = mixins;
return this;
}

public SerializationConfig setShouldIgnoreAnySetterAnnotation() {
this.shouldIgnoreAnySetterAnnotation = true;
return this;
}

public Map<Class<?>, Class<?>> getMixins() {
return mixins;
}

public boolean shouldIgnoreAnySetterAnnotation() {
return shouldIgnoreAnySetterAnnotation;
}

@Override
public String toString() {
return "SerializationConfig{" +
"mixins=" + mixins +
", shouldIgnoreAnySetterAnnotation=" + shouldIgnoreAnySetterAnnotation +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.taboola.backstage.internal.serialization;

import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

public class IgnoreAnySetterSerializationIntrospector extends JacksonAnnotationIntrospector {
@Override
public boolean hasAnySetterAnnotation(AnnotatedMethod am) {
return false;
}

@Override
public Boolean hasAnySetter(Annotated a) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.taboola.backstage.internal.serialization;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.taboola.backstage.internal.config.SerializationConfig;

public class SerializationMapperCreator {
public static ObjectMapper createObjectMapper(SerializationConfig serializationConfig) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
serializationConfig.getMixins().forEach(objectMapper::addMixIn);

if (serializationConfig.shouldIgnoreAnySetterAnnotation()) {
objectMapper.setAnnotationIntrospector(new IgnoreAnySetterSerializationIntrospector());
}

return objectMapper;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.taboola.backstage.internal;

import com.taboola.backstage.internal.config.CommunicationConfig;
import com.taboola.backstage.internal.config.SerializationConfig;
import org.junit.Assert;
import org.junit.Before;
import com.taboola.backstage.BackstageTestBase;
Expand All @@ -18,8 +19,9 @@ public class CommunicationFactoryTest extends BackstageTestBase {

@Before
public void beforeTest() {
CommunicationConfig config = new CommunicationConfig("http://localhost", "http://localhost", 1L, 1L, 1L, 1, 60L, "Dummy-Agent", true);
testInstance = new CommunicationFactory(config);
CommunicationConfig communicationConfig = new CommunicationConfig("http://localhost", "http://localhost", 1L, 1L, 1L, 1, 60L, "Dummy-Agent", true);
SerializationConfig serializationConfig = new SerializationConfig();
testInstance = new CommunicationFactory(communicationConfig, serializationConfig);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.taboola.backstage.internal.config;

import org.junit.Assert;
import org.junit.Test;

public class SerializationConfigTest {

@Test
public void serializationConfig_defaultConstructor_configInitializedWithDefaultValues() {
SerializationConfig serializationConfig = new SerializationConfig();
Assert.assertNotNull("Missing mixin", serializationConfig.getMixins());
Assert.assertEquals("Mixin is not empty by default", 0, serializationConfig.getMixins().size());
Assert.assertFalse("Wrong default value for shouldIgnoreAnySetterAnnotation", serializationConfig.shouldIgnoreAnySetterAnnotation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.taboola.backstage.internal.serialization;

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.taboola.backstage.internal.config.SerializationConfig;
import com.taboola.backstage.internal.serialization.SerializationMapperCreator;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class SerializationMapperCreatorTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void createObjectMapper_defaultSerializationConfig_objectMapperWithDefaultValues() {
ObjectMapper objectMapper = SerializationMapperCreator.createObjectMapper(new SerializationConfig());

Assert.assertEquals("Invalid property naming strategy", PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES, objectMapper.getPropertyNamingStrategy());
Assert.assertEquals("Invalid mixin count", 0, objectMapper.mixInCount());
}

@Test
public void createObjectMapper_serializationConfigWithMixins_objectMapperIsCreatedWithMixins() {
Map<Class<?>, Class<?>> mixins = new HashMap<>();
mixins.put(SampleApi.class, SampleMixIn.class);
SerializationConfig serializationConfig = new SerializationConfig().setMixins(mixins);
ObjectMapper objectMapper = SerializationMapperCreator.createObjectMapper(serializationConfig);

Assert.assertEquals("Invalid mixin count", 1, objectMapper.mixInCount());
Assert.assertEquals("Invalid property naming strategy", SampleMixIn.class, objectMapper.findMixInClassFor(SampleApi.class));
}

@Test
public void createObjectMapper_defaultSerializationConfigAndApiObjectHasAnySetter_anySetterIsCalledOnSerialization() throws IOException {
expectedException.expect(Exception.class);
expectedException.expectMessage("unknown field");
ObjectMapper objectMapper = SerializationMapperCreator.createObjectMapper(new SerializationConfig());

objectMapper.readValue("{ \"id\": 1, \"test\": \"test\" }", SampleApi.class);
}

@Test
public void createObjectMapper_serializationConfigWithoutAnySetterAndApiObjectHasAnySetter_anySetterIsCalledOnSerialization() throws IOException {
expectedException.expect(Exception.class);
expectedException.expectMessage("unknown field");
ObjectMapper objectMapper = SerializationMapperCreator.createObjectMapper(new SerializationConfig());

objectMapper.readValue("{ \"id\": 1, \"test\": \"test\" }", SampleApi.class);
}

@Test
public void createObjectMapper_serializationConfigWithIgnoreAnySetterAndApiObjectHasAnySetter_anySetterIgnored() throws IOException {
SerializationConfig serializationConfig = new SerializationConfig().setShouldIgnoreAnySetterAnnotation();
ObjectMapper objectMapper = SerializationMapperCreator.createObjectMapper(serializationConfig);

SampleApi sampleApi = objectMapper.readValue("{ \"id\": 1, \"test\": \"test\" }", SampleApi.class);
Assert.assertEquals("Id parsed incorrectly", 1, sampleApi.id);
Assert.assertNull("Name is parsed incorrectly", sampleApi.name);
}

private static class SampleApi {
@JsonProperty("id")
int id;

@JsonProperty("name")
String name;

@JsonAnySetter
public void handlerUnknownSetter(String field, Object value) throws Exception {
throw new Exception("unknown field");
}
}

private abstract class SampleMixIn {
@JsonIgnore
private String name;
}
}

0 comments on commit 8120ae1

Please sign in to comment.