/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2017 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.SerializationFeature;
import de.thm.arsnova.ImageUtils;
import de.thm.arsnova.connector.client.ConnectorClient;
import de.thm.arsnova.connector.client.ConnectorClientImpl;
import de.thm.arsnova.socket.ARSnovaSocket;
import de.thm.arsnova.socket.ARSnovaSocketIOServer;
import de.thm.arsnova.web.CacheControlInterceptorHandler;
import de.thm.arsnova.web.CorsFilter;
import de.thm.arsnova.web.DeprecatedApiInterceptorHandler;
import de.thm.arsnova.web.ResponseInterceptorHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Loads property file and configures non-security related beans and components.
*
* expose-proxy for AspectJ is needed to access the proxy object via AopContext.currentProxy() in CouchDBDao. It might
* have a negative impact on performance but is needed for caching until a better solution is implemented (e.g. use of
* AspectJ's weaving).
*/
@ComponentScan({
"de.thm.arsnova.aop",
"de.thm.arsnova.cache",
"de.thm.arsnova.controller",
"de.thm.arsnova.domain",
"de.thm.arsnova.dao",
"de.thm.arsnova.events",
"de.thm.arsnova.security",
"de.thm.arsnova.services",
"de.thm.arsnova.web"})
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableCaching
@EnableWebMvc
@PropertySource(
value = {"classpath:arsnova.properties.example", "file:/etc/arsnova/arsnova.properties"},
ignoreResourceNotFound = true
)
public class AppConfig extends WebMvcConfigurerAdapter {
public static final MediaType API_V2_MEDIA_TYPE = new MediaType("application", "vnd.de.thm.arsnova.v2+json");
public static final MediaType API_V3_MEDIA_TYPE = new MediaType("application", "vnd.de.thm.arsnova.v3+json");
@Autowired
private Environment env;
@Value(value = "${connector.enable}") private boolean connectorEnable;
@Value(value = "${connector.uri}") private String connectorUri;
@Value(value = "${connector.username}") private String connectorUsername;
@Value(value = "${connector.password}") private String connectorPassword;
@Value(value = "${socketio.bind-address}") private String socketAddress;
@Value(value = "${socketio.port}") private int socketPort;
@Value(value = "${socketio.ssl.jks-file:}") private String socketKeystore;
@Value(value = "${socketio.ssl.jks-password:}") private String socketKeystorePassword;
@Value(value = "${security.cors.origins:}") private String[] corsOrigins;
@Value(value = "${mail.host}") private String mailHost;
@Value(value = "${api.indent-response-body:false}") private boolean apiIndent;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(stringMessageConverter());
converters.add(defaultJsonMessageConverter());
converters.add(apiV2JsonMessageConverter());
//converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON_UTF8);
configurer.mediaType("xml", MediaType.APPLICATION_XML);
configurer.defaultContentType(API_V3_MEDIA_TYPE);
configurer.favorParameter(true);
configurer.favorPathExtension(false);
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(new InternalResourceViewResolver());
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(cacheControlInterceptorHandler());
registry.addInterceptor(deprecatedApiInterceptorHandler());
registry.addInterceptor(responseInterceptorHandler());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger.json").addResourceLocations("classpath:/");
}
@Bean
public CacheControlInterceptorHandler cacheControlInterceptorHandler() {
return new CacheControlInterceptorHandler();
}
@Bean
public DeprecatedApiInterceptorHandler deprecatedApiInterceptorHandler() {
return new DeprecatedApiInterceptorHandler();
}
@Bean
public ResponseInterceptorHandler responseInterceptorHandler() {
return new ResponseInterceptorHandler();
}
@Bean
public StringHttpMessageConverter stringMessageConverter() {
StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_PLAIN);
messageConverter.setSupportedMediaTypes(mediaTypes);
return messageConverter;
}
@Bean
public MappingJackson2HttpMessageConverter defaultJsonMessageConverter() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.defaultViewInclusion(false)
.indentOutput(apiIndent)
.simpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(API_V3_MEDIA_TYPE);
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
converter.setSupportedMediaTypes(mediaTypes);
return converter;
}
@Bean
public MappingJackson2HttpMessageConverter apiV2JsonMessageConverter() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.defaultViewInclusion(false)
.indentOutput(apiIndent)
.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.featuresToEnable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(API_V2_MEDIA_TYPE);
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
converter.setSupportedMediaTypes(mediaTypes);
return converter;
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setIgnoreUnresolvablePlaceholders(false);
return configurer;
}
@Bean
public PropertiesFactoryBean versionInfoProperties() {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("version.properties"));
return propertiesFactoryBean;
}
@Bean
public CorsFilter corsFilter() {
return new CorsFilter(Arrays.asList(corsOrigins));
}
@Bean(name = "connectorClient")
public ConnectorClient connectorClient() {
if (!connectorEnable) {
return null;
}
final ConnectorClientImpl connectorClient = new ConnectorClientImpl();
connectorClient.setServiceLocation(connectorUri);
connectorClient.setUsername(connectorUsername);
connectorClient.setPassword(connectorPassword);
return connectorClient;
}
@Profile("!test")
@Bean(name = "socketServer", initMethod = "startServer", destroyMethod = "stopServer")
public ARSnovaSocket socketServer() {
final ARSnovaSocketIOServer socketServer = new ARSnovaSocketIOServer();
socketServer.setHostIp(socketAddress);
socketServer.setPortNumber(socketPort);
socketServer.setUseSSL(!socketKeystore.isEmpty());
socketServer.setKeystore(socketKeystore);
socketServer.setStorepass(socketKeystorePassword);
return socketServer;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
@Bean
public JavaMailSenderImpl mailSender() {
final JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(mailHost);
return mailSender;
}
@Bean
public ImageUtils imageUtils() {
return new ImageUtils();
}
}