package net.ttddyy.evernote.rest;
import com.evernote.auth.EvernoteService;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.*;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.social.evernote.api.Evernote;
import org.springframework.social.evernote.api.impl.EvernoteTemplate;
import org.springframework.social.evernote.connect.EvernoteConnectionFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.WebRequest;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* Evernote Rest Webapp application and configuration.
*
* @author Tadaya Tsuyukubo
*/
@ComponentScan
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties
public class Application {
@Autowired
public EvernotePropertiesConfiguration evernotePropertiesConfiguration;
@Configuration
@ConfigurationProperties("evernote")
public static class EvernotePropertiesConfiguration {
@NotNull
public String consumerKey;
@NotNull
public String consumerSecret;
public String accessToken;
public boolean alwaysUseTokenFromConfig;
public boolean fallbackToTokenFromConfig;
public EvernoteService environment = EvernoteService.SANDBOX; // default is sandbox
public String userAgent;
public Map<String, String> customHeaders = new HashMap<String, String>();
public void setEnvironment(EvernoteService environment) {
this.environment = environment;
}
public void setConsumerKey(String consumerKey) {
this.consumerKey = consumerKey;
}
public void setConsumerSecret(String consumerSecret) {
this.consumerSecret = consumerSecret;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public void setAlwaysUseTokenFromConfig(boolean alwaysUseTokenFromConfig) {
this.alwaysUseTokenFromConfig = alwaysUseTokenFromConfig;
}
public void setFallbackToTokenFromConfig(boolean fallbackToTokenFromConfig) {
this.fallbackToTokenFromConfig = fallbackToTokenFromConfig;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public void setCustomHeaders(Map<String, String> customHeaders) {
this.customHeaders = customHeaders;
}
// getter is required for map to bind property values
public Map<String, String> getCustomHeaders() {
return customHeaders;
}
}
@Bean
public EvernoteConnectionFactory evernoteConnectionFactory() {
final String consumerKey = this.evernotePropertiesConfiguration.consumerKey;
final String consumerSecret = this.evernotePropertiesConfiguration.consumerSecret;
final EvernoteService evernoteService = this.evernotePropertiesConfiguration.environment;
return new EvernoteConnectionFactory(consumerKey, consumerSecret, evernoteService);
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
public Evernote evernote(WebRequest request) {
final EvernotePropertiesConfiguration config = this.evernotePropertiesConfiguration;
final EvernoteService evernoteService = config.environment;
final Evernote evernote;
if (config.alwaysUseTokenFromConfig) {
evernote = new EvernoteTemplate(evernoteService, config.accessToken);
} else {
String accessToken = request.getHeader("evernote-rest-accesstoken");
if (accessToken == null && config.fallbackToTokenFromConfig) {
accessToken = config.accessToken; // fallback to accesstoken from config
}
final String noteStoreUrl = request.getHeader("evernote-rest-notestoreurl");
final String webApiUrlPrefix = request.getHeader("evernote-rest-webapiurlprefix");
final String userId = request.getHeader("evernote-rest-userid");
if (noteStoreUrl != null && webApiUrlPrefix != null && userId != null) {
evernote = new EvernoteTemplate(evernoteService, accessToken, noteStoreUrl, webApiUrlPrefix, userId);
} else {
evernote = new EvernoteTemplate(evernoteService, accessToken);
}
}
// for this rest app, do not create proxy for thrift object
evernote.setApplyNullSafe(false);
if (config.userAgent != null) {
evernote.clientFactory().setUserAgent(config.userAgent);
}
final Map<String, String> customHeaders = config.getCustomHeaders();
if (!customHeaders.isEmpty()) {
evernote.clientFactory().setCustomHeaders(customHeaders);
}
return evernote;
}
/**
* override spring-boot default ObjectMapper to configure output(serialization) json.
*
* @see org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration.ObjectMappers#jacksonObjectMapper()
*/
@Bean
public ObjectMapper jacksonObjectMapper() {
// use different visibility for serialization(output json)
// I want to ONLY change the visibility for serialization, but couldn't find nice way to do it.
// maybe related to this issue: https://github.com/FasterXML/jackson-databind/issues/352
// for now, override ObjectMapper and set new SerializationConfig in instance initializer.
// TODO: find correct way to do this.
final ObjectMapper mapper = new ObjectMapper() {
{
// use instance fields for output json
_serializationConfig = _serializationConfig.with(
_serializationConfig.getDefaultVisibilityChecker()
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE)
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
);
}
};
// mix-in to ignore thrift specific fields for serialization.
mapper.addMixInAnnotations(Object.class, ThriftPropertyJacksonFilter.class);
return mapper;
}
/**
* Mix-in class for Jackson to ignore thrift specific fields in serialization.
*/
@JsonIgnoreProperties("__isset_vector")
private static abstract class ThriftPropertyJacksonFilter {
}
/**
* To maximize the caching feature in name discoverer, inject as a bean instead of creating every time.
*/
@Bean
public ParameterNameDiscoverer parameterNameDiscoverer() {
return new DefaultParameterNameDiscoverer();
}
@Bean
public ParameterJavaTypeDiscoverer parameterJavaTypeDiscoverer() {
return new ParameterJavaTypeDiscoverer();
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
/**
* To initialize application when it is deployed to web container as a war file.
*/
public static class ApplicationServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
}