Files
Athou_commafeed/commafeed-server/src/main/java/com/commafeed/CommaFeedApplication.java

250 lines
11 KiB
Java
Raw Normal View History

package com.commafeed;
2014-10-26 17:48:25 +01:00
import java.io.IOException;
import java.time.Instant;
2014-08-22 20:17:13 +02:00
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
2021-01-30 22:40:31 +01:00
import java.util.concurrent.TimeUnit;
2014-08-22 20:17:13 +02:00
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.hibernate.cfg.AvailableSettings;
2021-01-30 22:40:31 +01:00
import com.codahale.metrics.json.MetricsModule;
import com.commafeed.backend.dao.UserDAO;
2023-05-01 11:20:44 +02:00
import com.commafeed.backend.feed.FeedRefreshEngine;
import com.commafeed.backend.model.AbstractModel;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.service.DatabaseStartupService;
import com.commafeed.backend.service.UserService;
import com.commafeed.backend.task.ScheduledTask;
import com.commafeed.frontend.auth.PasswordConstraintValidator;
2014-10-28 16:31:49 +01:00
import com.commafeed.frontend.auth.SecurityCheckFactoryProvider;
import com.commafeed.frontend.resource.AdminREST;
import com.commafeed.frontend.resource.CategoryREST;
import com.commafeed.frontend.resource.EntryREST;
import com.commafeed.frontend.resource.FeedREST;
import com.commafeed.frontend.resource.ServerREST;
import com.commafeed.frontend.resource.UserREST;
2023-08-17 17:25:27 +02:00
import com.commafeed.frontend.resource.fever.FeverREST;
2014-08-11 05:51:44 +02:00
import com.commafeed.frontend.servlet.CustomCssServlet;
import com.commafeed.frontend.servlet.CustomJsServlet;
2014-08-09 15:25:41 +02:00
import com.commafeed.frontend.servlet.LogoutServlet;
2014-08-09 13:25:53 +02:00
import com.commafeed.frontend.servlet.NextUnreadServlet;
import com.commafeed.frontend.servlet.RobotsTxtDisallowAllServlet;
2014-10-28 16:31:49 +01:00
import com.commafeed.frontend.session.SessionHelperFactoryProvider;
import com.commafeed.frontend.ws.WebSocketConfigurator;
import com.commafeed.frontend.ws.WebSocketEndpoint;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
2014-08-17 14:16:30 +02:00
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import io.dropwizard.assets.AssetsBundle;
import io.dropwizard.configuration.DefaultConfigurationFactoryFactory;
import io.dropwizard.configuration.EnvironmentVariableSubstitutor;
import io.dropwizard.configuration.SubstitutingSourceProvider;
2023-12-17 14:11:15 +01:00
import io.dropwizard.core.Application;
import io.dropwizard.core.setup.Bootstrap;
import io.dropwizard.core.setup.Environment;
import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.forms.MultiPartBundle;
import io.dropwizard.hibernate.HibernateBundle;
import io.dropwizard.migrations.MigrationsBundle;
import io.dropwizard.servlets.CacheBustingFilter;
import io.whitfin.dropwizard.configuration.EnvironmentSubstitutor;
2023-12-17 14:11:15 +01:00
import jakarta.servlet.DispatcherType;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.websocket.server.ServerEndpointConfig;
public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
public static final String USERNAME_ADMIN = "admin";
public static final String USERNAME_DEMO = "demo";
public static final Instant STARTUP_TIME = Instant.now();
private HibernateBundle<CommaFeedConfiguration> hibernateBundle;
2014-08-19 12:51:15 +02:00
@Override
public String getName() {
return "CommaFeed";
}
@Override
public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) {
2023-12-27 08:52:24 +01:00
configureEnvironmentSubstitutor(bootstrap);
configureObjectMapper(bootstrap.getObjectMapper());
2021-01-30 22:40:31 +01:00
2023-12-27 08:52:24 +01:00
bootstrap.addBundle(hibernateBundle = new HibernateBundle<>(AbstractModel.class, Feed.class, FeedCategory.class, FeedEntry.class,
FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class, FeedSubscription.class, User.class, UserRole.class,
UserSettings.class) {
@Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
DataSourceFactory factory = configuration.getDataSourceFactory();
factory.getProperties().put(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, "pooled-lo");
2015-03-19 11:13:34 +01:00
factory.getProperties().put(AvailableSettings.STATEMENT_BATCH_SIZE, "50");
factory.getProperties().put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
2023-05-27 08:38:23 +02:00
factory.getProperties().put(AvailableSettings.ORDER_INSERTS, "true");
factory.getProperties().put(AvailableSettings.ORDER_UPDATES, "true");
return factory;
}
2014-08-17 14:16:30 +02:00
});
2023-12-27 08:52:24 +01:00
bootstrap.addBundle(new MigrationsBundle<>() {
@Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
return configuration.getDataSourceFactory();
}
});
bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html"));
2015-02-20 16:41:20 +01:00
bootstrap.addBundle(new MultiPartBundle());
}
2023-12-27 08:52:24 +01:00
private static void configureEnvironmentSubstitutor(Bootstrap<CommaFeedConfiguration> bootstrap) {
bootstrap.setConfigurationFactoryFactory(new DefaultConfigurationFactoryFactory<>() {
@Override
protected ObjectMapper configureObjectMapper(ObjectMapper objectMapper) {
// disable case sensitivity because EnvironmentSubstitutor maps MYPROPERTY to myproperty and not to myProperty
return objectMapper
.setConfig(objectMapper.getDeserializationConfig().with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
}
});
bootstrap.setConfigurationSourceProvider(buildEnvironmentSubstitutor(bootstrap));
}
private static void configureObjectMapper(ObjectMapper objectMapper) {
// read and write instants as milliseconds instead of nanoseconds
objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
// add support for serializing metrics
objectMapper.registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
}
2023-12-27 08:52:24 +01:00
private static EnvironmentSubstitutor buildEnvironmentSubstitutor(Bootstrap<CommaFeedConfiguration> bootstrap) {
// enable config.yml string substitution
// e.g. having a custom config.yml file with app.session.path=${SOME_ENV_VAR} will substitute SOME_ENV_VAR
SubstitutingSourceProvider substitutingSourceProvider = new SubstitutingSourceProvider(bootstrap.getConfigurationSourceProvider(),
new EnvironmentVariableSubstitutor(false));
// enable config.yml properties override with env variables prefixed with CF_
// e.g. setting CF_APP_ALLOWREGISTRATIONS=true will set app.allowRegistrations to true
return new EnvironmentSubstitutor("CF", substitutingSourceProvider);
}
@Override
2023-12-27 08:52:24 +01:00
public void run(CommaFeedConfiguration config, Environment environment) {
PasswordConstraintValidator.setStrict(config.getApplicationSettings().getStrictPasswordPolicy());
2014-08-17 14:16:30 +02:00
// guice init
Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));
2014-10-29 05:03:17 +01:00
// session management
2023-08-21 18:26:55 +02:00
environment.servlets().setSessionHandler(config.getSessionHandlerFactory().build(config.getDataSourceFactory()));
2014-10-29 05:03:17 +01:00
2014-11-24 12:52:57 +01:00
// support for "@SecurityCheck User user" injection
environment.jersey()
.register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserDAO.class),
injector.getInstance(UserService.class), config));
2014-11-24 12:52:57 +01:00
// support for "@Context SessionHelper sessionHelper" injection
2014-10-28 16:31:49 +01:00
environment.jersey().register(new SessionHelperFactoryProvider.Binder());
2014-08-09 15:25:41 +02:00
// REST resources
environment.jersey().setUrlPattern("/rest/*");
2014-08-17 14:16:30 +02:00
environment.jersey().register(injector.getInstance(AdminREST.class));
environment.jersey().register(injector.getInstance(CategoryREST.class));
environment.jersey().register(injector.getInstance(EntryREST.class));
environment.jersey().register(injector.getInstance(FeedREST.class));
environment.jersey().register(injector.getInstance(ServerREST.class));
environment.jersey().register(injector.getInstance(UserREST.class));
2023-08-17 17:25:27 +02:00
environment.jersey().register(injector.getInstance(FeverREST.class));
2014-08-09 15:25:41 +02:00
// Servlets
2014-08-17 14:16:30 +02:00
environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout");
environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css");
environment.servlets().addServlet("customJs", injector.getInstance(CustomJsServlet.class)).addMapping("/custom_js.js");
if (Boolean.TRUE.equals(config.getApplicationSettings().getHideFromWebCrawlers())) {
environment.servlets()
.addServlet("robots.txt", injector.getInstance(RobotsTxtDisallowAllServlet.class))
.addMapping("/robots.txt");
}
2014-08-09 13:25:53 +02:00
// WebSocket endpoint
JakartaWebSocketServletContainerInitializer.configure(environment.getApplicationContext(), (context, container) -> {
container.setDefaultMaxSessionIdleTimeout(config.getApplicationSettings().getWebsocketPingInterval().toMilliseconds() + 10000);
container.addEndpoint(ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/ws")
.configurator(injector.getInstance(WebSocketConfigurator.class))
.build());
});
2014-08-18 13:09:54 +02:00
// Scheduled tasks
2023-12-27 08:52:24 +01:00
Set<ScheduledTask> tasks = injector.getInstance(Key.get(new TypeLiteral<>() {
}));
2022-01-02 21:32:40 +01:00
ScheduledExecutorService executor = environment.lifecycle()
.scheduledExecutorService("task-scheduler", true)
.threads(tasks.size())
.build();
for (ScheduledTask task : tasks) {
task.register(executor);
}
2014-08-09 22:18:14 +02:00
2014-08-18 13:09:54 +02:00
// database init/changelogs
environment.lifecycle().manage(injector.getInstance(DatabaseStartupService.class));
2014-08-18 13:09:54 +02:00
// start feed fetching engine
environment.lifecycle().manage(injector.getInstance(FeedRefreshEngine.class));
2014-08-09 13:25:53 +02:00
// prevent caching index.html, so that the webapp is always up to date
environment.servlets()
.addFilter("index-cache-busting-filter", new CacheBustingFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/");
// prevent caching openapi files, so that the documentation is always up to date
environment.servlets()
.addFilter("openapi-cache-busting-filter", new CacheBustingFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/openapi.json", "/openapi.yaml");
// prevent caching REST resources, except for favicons
environment.servlets().addFilter("rest-cache-busting-filter", new CacheBustingFilter() {
2014-10-26 17:48:25 +01:00
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = ((HttpServletRequest) request).getRequestURI();
if (path.contains("/feed/favicon")) {
chain.doFilter(request, response);
} else {
super.doFilter(request, response, chain);
}
}
}).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/rest/*");
2014-10-27 09:54:06 +01:00
}
public static void main(String[] args) throws Exception {
new CommaFeedApplication().run(args);
}
}