package org.molgenis.ui; import com.google.common.collect.Maps; import freemarker.template.Configuration; import freemarker.template.TemplateException; import org.molgenis.data.convert.DateToStringConverter; import org.molgenis.data.convert.StringToDateConverter; import org.molgenis.data.i18n.LanguageService; import org.molgenis.data.platform.config.PlatformConfig; import org.molgenis.data.settings.AppSettings; import org.molgenis.file.FileStore; import org.molgenis.framework.ui.MolgenisPluginRegistry; import org.molgenis.framework.ui.MolgenisPluginRegistryImpl; import org.molgenis.messageconverter.CsvHttpMessageConverter; import org.molgenis.security.CorsInterceptor; import org.molgenis.security.core.MolgenisPermissionService; import org.molgenis.security.freemarker.HasPermissionDirective; import org.molgenis.security.freemarker.NotHasPermissionDirective; import org.molgenis.ui.converter.RdfConverter; import org.molgenis.ui.freemarker.LimitMethod; import org.molgenis.ui.freemarker.MolgenisFreemarkerObjectWrapper; import org.molgenis.ui.menu.MenuMolgenisUi; import org.molgenis.ui.menu.MenuReaderService; import org.molgenis.ui.menu.MenuReaderServiceImpl; import org.molgenis.ui.menumanager.MenuManagerService; import org.molgenis.ui.menumanager.MenuManagerServiceImpl; import org.molgenis.ui.security.MolgenisUiPermissionDecorator; import org.molgenis.util.ApplicationContextProvider; import org.molgenis.util.GsonHttpMessageConverter; import org.molgenis.util.ResourceFingerprintRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.BufferedImageHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Properties; import static freemarker.template.Configuration.VERSION_2_3_23; import static org.molgenis.framework.ui.ResourcePathPatterns.*; @Import({PlatformConfig.class, RdfConverter.class}) public abstract class MolgenisWebAppConfig extends WebMvcConfigurerAdapter { @Autowired private AppSettings appSettings; @Autowired private MolgenisPermissionService molgenisPermissionService; @Autowired private GsonHttpMessageConverter gsonHttpMessageConverter; @Autowired private RdfConverter rdfConverter; @Autowired private LanguageService languageService; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { int cachePeriod; if (environment.equals("development")) { cachePeriod = 0; } else { cachePeriod = 31536000; // a year } registry.addResourceHandler(PATTERN_CSS).addResourceLocations("/css/", "classpath:/css/") .setCachePeriod(cachePeriod); registry.addResourceHandler(PATTERN_IMG).addResourceLocations("/img/", "classpath:/img/") .setCachePeriod(cachePeriod); registry.addResourceHandler(PATTERN_JS).addResourceLocations("/js/", "classpath:/js/") .setCachePeriod(cachePeriod); registry.addResourceHandler(PATTERN_FONTS).addResourceLocations("/fonts/", "classpath:/fonts/") .setCachePeriod(cachePeriod); registry.addResourceHandler("/generated-doc/**").addResourceLocations("/generated-doc/").setCachePeriod(3600); registry.addResourceHandler("/html/**").addResourceLocations("/html/", "classpath:/html/").setCachePeriod(3600); } @Value("${environment:production}") private String environment; @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(gsonHttpMessageConverter); converters.add(new BufferedImageHttpMessageConverter()); converters.add(new CsvHttpMessageConverter()); converters.add(new ResourceHttpMessageConverter()); converters.add(rdfConverter); } @Override public void configurePathMatch(PathMatchConfigurer configurer) { // Fix for https://github.com/molgenis/molgenis/issues/5431 configurer.setUseRegisteredSuffixPatternMatch(true); } @Bean public MappedInterceptor mappedCorsInterceptor() { /* * This way, the cors interceptor is added to the resource handlers as well, if the patterns overlap. * * See https://jira.spring.io/browse/SPR-10655 */ return new MappedInterceptor(new String[] { "/api/**", "/fdp/**" }, corsInterceptor()); } @Override public void addInterceptors(InterceptorRegistry registry) { String pluginInterceptPattern = MolgenisPluginController.PLUGIN_URI_PREFIX + "**"; registry.addInterceptor(molgenisInterceptor()); registry.addInterceptor(molgenisPluginInterceptor()).addPathPatterns(pluginInterceptPattern); } @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new DateToStringConverter()); registry.addConverter(new StringToDateConverter()); } @Bean public ResourceFingerprintRegistry resourceFingerprintRegistry() { return new ResourceFingerprintRegistry(); } @Bean public MolgenisInterceptor molgenisInterceptor() { return new MolgenisInterceptor(resourceFingerprintRegistry(), appSettings, languageService, environment); } @Bean public MolgenisPluginInterceptor molgenisPluginInterceptor() { return new MolgenisPluginInterceptor(molgenisUi(), molgenisPermissionService); } @Bean public static PropertySourcesPlaceholderConfigurer properties() { PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer(); Resource[] resources = new Resource[] { new FileSystemResource(System.getProperty("molgenis.home") + "/molgenis-server.properties"), new ClassPathResource("/molgenis.properties") }; pspc.setLocations(resources); pspc.setFileEncoding("UTF-8"); pspc.setIgnoreUnresolvablePlaceholders(true); pspc.setIgnoreResourceNotFound(true); pspc.setNullValue("@null"); return pspc; } @Value("${mail.host:smtp.gmail.com}") private String mailHost; @Value("${mail.port:587}") private Integer mailPort; @Value("${mail.protocol:smtp}") private String mailProtocol; @Value("${mail.username}") private String mailUsername; // specify in molgenis-server.properties @Value("${mail.password}") private String mailPassword; // specify in molgenis-server.properties @Value("${mail.java.auth:true}") private String mailJavaAuth; @Value("${mail.java.starttls.enable:true}") private String mailJavaStartTlsEnable; @Value("${mail.java.quitwait:false}") private String mailJavaQuitWait; @Bean public JavaMailSender mailSender() { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); mailSender.setHost(mailHost); mailSender.setPort(mailPort); mailSender.setProtocol(mailProtocol); mailSender.setUsername(mailUsername); // specify in molgenis-server.properties mailSender.setPassword(mailPassword); // specify in molgenis-server.properties Properties javaMailProperties = new Properties(); javaMailProperties.setProperty("mail.smtp.auth", mailJavaAuth); javaMailProperties.setProperty("mail.smtp.starttls.enable", mailJavaStartTlsEnable); javaMailProperties.setProperty("mail.smtp.quitwait", mailJavaQuitWait); mailSender.setJavaMailProperties(javaMailProperties); return mailSender; } @Bean public FileStore fileStore() { // get molgenis home directory String molgenisHomeDir = System.getProperty("molgenis.home"); if (molgenisHomeDir == null) { throw new IllegalArgumentException("missing required java system property 'molgenis.home'"); } if (!molgenisHomeDir.endsWith(File.separator)) molgenisHomeDir = molgenisHomeDir + File.separator; // create molgenis store directory in molgenis data directory if not exists String molgenisFileStoreDirStr = molgenisHomeDir + "data" + File.separator + "filestore"; File molgenisDataDir = new File(molgenisFileStoreDirStr); if (!molgenisDataDir.exists()) { if (!molgenisDataDir.mkdirs()) { throw new RuntimeException("failed to create directory: " + molgenisFileStoreDirStr); } } return new FileStore(molgenisFileStoreDirStr); } /** * Bean that allows referencing Spring managed beans from Java code which is not managed by Spring * * @return */ @Bean public ApplicationContextProvider applicationContextProvider() { return new ApplicationContextProvider(); } /** * Enable spring freemarker viewresolver. All freemarker template names should end with '.ftl' */ @Bean public ViewResolver viewResolver() { FreeMarkerViewResolver resolver = new FreeMarkerViewResolver(); resolver.setCache(true); resolver.setSuffix(".ftl"); resolver.setContentType("text/html;charset=UTF-8"); return resolver; } /** * Configure freemarker. All freemarker templates should be on the classpath in a package called 'freemarker' * * @throws TemplateException * @throws IOException */ @Bean public FreeMarkerConfigurer freeMarkerConfigurer() throws IOException, TemplateException { FreeMarkerConfigurer result = new FreeMarkerConfigurer() { @Override protected void postProcessConfiguration(Configuration config) throws IOException, TemplateException { config.setObjectWrapper(new MolgenisFreemarkerObjectWrapper(VERSION_2_3_23)); } }; result.setPreferFileSystemAccess(false); result.setTemplateLoaderPath("classpath:/templates/"); result.setDefaultEncoding("UTF-8"); Properties freemarkerSettings = new Properties(); freemarkerSettings.setProperty(Configuration.LOCALIZED_LOOKUP_KEY, Boolean.FALSE.toString()); result.setFreemarkerSettings(freemarkerSettings); Map<String, Object> freemarkerVariables = Maps.newHashMap(); freemarkerVariables.put("limit", new LimitMethod()); freemarkerVariables.put("hasPermission", new HasPermissionDirective(molgenisPermissionService)); freemarkerVariables.put("notHasPermission", new NotHasPermissionDirective(molgenisPermissionService)); addFreemarkerVariables(freemarkerVariables); result.setFreemarkerVariables(freemarkerVariables); return result; } // Override in subclass if you need more freemarker variables protected void addFreemarkerVariables(Map<String, Object> freemarkerVariables) { } @Bean public MultipartResolver multipartResolver() { return new StandardServletMultipartResolver(); } @Bean public MenuReaderService menuReaderService() { return new MenuReaderServiceImpl(appSettings); } @Bean public MenuManagerService menuManagerService() { return new MenuManagerServiceImpl(menuReaderService(), appSettings, molgenisPluginRegistry()); } @Bean public MolgenisUi molgenisUi() { MolgenisUi molgenisUi = new MenuMolgenisUi(menuReaderService()); return new MolgenisUiPermissionDecorator(molgenisUi, molgenisPermissionService); } @Bean public MolgenisPluginRegistry molgenisPluginRegistry() { return new MolgenisPluginRegistryImpl(); } @Bean public CorsInterceptor corsInterceptor() { return new CorsInterceptor(); } @PostConstruct public void validateMolgenisServerProperties() { // validate properties defined in molgenis-server.properties String path = System.getProperty("molgenis.home") + File.separator + "molgenis-server.properties"; if (environment == null) { throw new RuntimeException("Missing required property 'environment' in " + path + ", allowed values are [development, production]."); } else if (!environment.equals("development") && !environment.equals("production")) { throw new RuntimeException("Invalid value '" + environment + "' for property 'environment' in " + path + ", allowed values are [development, production]."); } } }