Ctan.java
/*
* Copyright © 2022-2025 The CTAN Team and individual authors
*
* This file is distributed under the 3-clause BSD license.
* See file LICENSE for details.
*/
package org.ctan.site;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.EnumSet;
import org.ctan.site.command.InitSearchCommand;
import org.ctan.site.domain.account.User.CtanPrincipal;
import org.ctan.site.health.AppHealthCheck;
import org.ctan.site.health.ContentHealthCheck;
import org.ctan.site.health.CtanAnnHealthCheck;
import org.ctan.site.health.IncomingHealthCheck;
import org.ctan.site.health.TexarchiveHealthCheck;
import org.ctan.site.resources.Search3Resource;
import org.ctan.site.resources.Sitemap3Resource;
import org.ctan.site.resources.admin.CrudAuthor3Resource;
import org.ctan.site.resources.admin.CrudGuestbook3Resource;
import org.ctan.site.resources.admin.CrudLicense3Resource;
import org.ctan.site.resources.admin.CrudUpload3Resource;
import org.ctan.site.resources.admin.CrudUser3Resource;
import org.ctan.site.resources.admin.CrudVote3Resource;
import org.ctan.site.resources.admin.Stopword3Resource;
import org.ctan.site.resources.admin.TexArchiveNotes3Resource;
import org.ctan.site.resources.admin.Ticket3Resource;
import org.ctan.site.resources.admin.UserStopword3Resource;
import org.ctan.site.resources.catalogue.Author3Resource;
import org.ctan.site.resources.catalogue.Bibtex3Resource;
import org.ctan.site.resources.catalogue.License3Resource;
import org.ctan.site.resources.catalogue.Lug3Resource;
import org.ctan.site.resources.catalogue.Pkg3Resource;
import org.ctan.site.resources.catalogue.Topic3Resource;
import org.ctan.site.resources.catalogue.Version3Resource;
import org.ctan.site.resources.catalogue.Vote3Resource;
import org.ctan.site.resources.catalogue.api.JsonAuthorResource;
import org.ctan.site.resources.catalogue.api.JsonLicenseResource;
import org.ctan.site.resources.catalogue.api.JsonPkgResource;
import org.ctan.site.resources.catalogue.api.JsonTopicResource;
import org.ctan.site.resources.catalogue.api.JsonVersionResource;
import org.ctan.site.resources.catalogue.api.SearchResource;
import org.ctan.site.resources.catalogue.api.SubmitResource;
import org.ctan.site.resources.catalogue.api.XmlAuthorResource;
import org.ctan.site.resources.catalogue.api.XmlDtdResource;
import org.ctan.site.resources.catalogue.api.XmlLicenseResource;
import org.ctan.site.resources.catalogue.api.XmlPkgResource;
import org.ctan.site.resources.catalogue.api.XmlTopicResource;
import org.ctan.site.resources.catalogue.api.XmlVersionResource;
import org.ctan.site.resources.content.Content3Resource;
import org.ctan.site.resources.mirrors.MirrMon3Resource;
import org.ctan.site.resources.mirrors.MirrorRegistration3Resource;
import org.ctan.site.resources.mirrors.Mirrors3Resource;
import org.ctan.site.resources.postings.Atom10Resource;
import org.ctan.site.resources.postings.Postings3Resource;
import org.ctan.site.resources.postings.Rss20Resource;
import org.ctan.site.resources.site.CtanSite3Resource;
import org.ctan.site.resources.site.GuestBook3Resource;
import org.ctan.site.resources.site.Message3Resource;
import org.ctan.site.resources.site.Role3Resource;
import org.ctan.site.resources.site.User3Resource;
import org.ctan.site.resources.texarchive.Texarchive3Resource;
import org.ctan.site.resources.upload.Upload3Resource;
import org.ctan.site.services.SitemapService;
import org.ctan.site.services.account.AccountService;
import org.ctan.site.services.catalogue.BibtexService;
import org.ctan.site.services.catalogue.CatalogueImportService;
import org.ctan.site.services.catalogue.VoteService;
import org.ctan.site.services.content.ContentService;
import org.ctan.site.services.content.LionService;
import org.ctan.site.services.mail.MailService;
import org.ctan.site.services.mirrors.MirrMonService;
import org.ctan.site.services.mirrors.MirrorRegistrationService;
import org.ctan.site.services.mirrors.MirrorService;
import org.ctan.site.services.postings.PostingsService;
import org.ctan.site.services.search.SearchService;
import org.ctan.site.services.search.base.IndexingService;
import org.ctan.site.services.search.base.IndexingSession;
import org.ctan.site.services.texarchive.ArchiveFilesUpdateService;
import org.ctan.site.services.texarchive.PkgService;
import org.ctan.site.services.texarchive.TexArchiveService;
import org.ctan.site.services.upload.Submit11Service;
import org.ctan.site.services.upload.SubmitService;
import org.ctan.site.services.upload.UploadService;
import org.ctan.site.stores.ArchiveFileStore;
import org.ctan.site.stores.AuthorStore;
import org.ctan.site.stores.GuestBookStore;
import org.ctan.site.stores.LicenseStore;
import org.ctan.site.stores.LugStore;
import org.ctan.site.stores.MessageStore;
import org.ctan.site.stores.MirrorRegistrationStore;
import org.ctan.site.stores.MirrorStore;
import org.ctan.site.stores.PkgAliasStore;
import org.ctan.site.stores.PkgStore;
import org.ctan.site.stores.StopwordStore;
import org.ctan.site.stores.TexArchiveNotesStore;
import org.ctan.site.stores.TicketStore;
import org.ctan.site.stores.TopicStore;
import org.ctan.site.stores.UploadStore;
import org.ctan.site.stores.UserStopwordStore;
import org.ctan.site.stores.UserStore;
import org.ctan.site.stores.VoteStore;
import org.ctan.site.tasks.ArchiveFilesUpdateTask;
import org.ctan.site.tasks.CatalogueUpdateTask;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.hibernate.SessionFactory;
import com.codahale.metrics.health.HealthCheckRegistry;
import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.auth.AuthValueFactoryProvider;
import io.dropwizard.configuration.EnvironmentVariableSubstitutor;
import io.dropwizard.configuration.SubstitutingSourceProvider;
import io.dropwizard.core.Application;
import io.dropwizard.core.setup.AdminEnvironment;
import io.dropwizard.core.setup.Bootstrap;
import io.dropwizard.core.setup.Environment;
import io.dropwizard.forms.MultiPartBundle;
import io.dropwizard.jersey.setup.JerseyEnvironment;
import jakarta.servlet.DispatcherType;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
/**
* The class <code>Ctan</code> contains the command line interface to start the
* Dropwizard server.
*
* @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
*/
@Slf4j
public class Ctan extends Application<CtanConfiguration> {
/**
* The method <code>main</code> provides means to run the Dropwizard
* application from the command line.
*
* @param args the command line arguments
*/
public static void main(String[] args) {
try {
new Ctan().run(args);
} catch (Exception e) {
log.error("Exception in main()", e);
System.exit(1);
}
}
/**
* The method <code>configureAuth</code> provides means to configure the
* authentication.
*
* @param jersey the environment
* @param userStore the user store
*/
private void configureAuth(JerseyEnvironment jersey, UserStore userStore) {
jersey.register(new AuthDynamicFeature(new CtanAuthFilter(userStore)));
jersey.register(RolesAllowedDynamicFeature.class);
// If you want to use @Auth to inject a custom Principal type into your
// resource
jersey.register(
new AuthValueFactoryProvider.Binder<>(CtanPrincipal.class));
}
/**
* The method <code>configureCors</code> provides means to configure the
* treatment of CORS.
*
* @param environment the environment
*/
private void configureCors(Environment environment) {
var cors = environment
.servlets()
.addFilter("CORS", CrossOriginFilter.class);
cors.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM,
"X-Requested-With,"
+ "Content-Type,"
+ "Accept,"
+ "Origin");
// cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM,
// "X-Requested-With,"
// + "Content-Type,"
// + "Accept,"
// + "Origin,"
// + "Authorization,"
// + "Authentication");
cors.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM,
"OPTIONS,GET,PUT,POST,DELETE,HEAD");
cors.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM,
"true");
cors.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true,
"/*");
}
/**
* {@inheritDoc}
*
* @see io.dropwizard.core.Application#initialize(io.dropwizard.core.setup.Bootstrap)
*/
@Override
public void initialize(Bootstrap<CtanConfiguration> bootstrap) {
bootstrap.setConfigurationSourceProvider(new SubstitutingSourceProvider(
bootstrap.getConfigurationSourceProvider(),
new EnvironmentVariableSubstitutor(false)));
bootstrap.addBundle(CtanDatabaseBundles.SITE_DB_BUNDLE);
bootstrap.addBundle(CtanDatabaseBundles.MIRRORS_DB_BUNDLE);
bootstrap.addBundle(new MultiPartBundle());
bootstrap.addCommand(new InitSearchCommand());
// bootstrap.addBundle(new FlywayBundle<CtanConfiguration>() {
//
// @Override
// public DataSourceFactory getDataSourceFactory(
// CtanConfiguration configuration) {
//
// return configuration.getDataSourceFactory();
// }
//
// @Override
// public FlywayFactory getFlywayFactory(
// CtanConfiguration configuration) {
//
// return configuration.getFlywayFactory();
// }
// });
}
/**
* The method <code>registerHealthChecks</code> provides means to add health
* checks to the registry.
*
* @param healthChecks the target registry
* @param config the configuration
*/
private void registerHealthChecks(HealthCheckRegistry healthChecks,
CtanConfiguration config) {
log.info("Registering health checks");
healthChecks.register("AppHealthCheck",
new AppHealthCheck(config));
healthChecks.register("TexarchiveHealthCheck",
new TexarchiveHealthCheck(config));
healthChecks.register("ContentHealthCheck",
new ContentHealthCheck(config));
healthChecks.register("CtanAnnHealthCheck",
new CtanAnnHealthCheck(config));
healthChecks.register("IncomingHealthCheck",
new IncomingHealthCheck(config));
// healthChecks.register("SiteDbHealthCheck",
// new SiteDbHealthCheck(config));
}
/**
* The method <code>registerPublishedApi</code> provides means to register
* the published API services to interact with JSON and XML.
*
* @param jersey the environment instance
* @param pkgStore the package store
* @param authorStore the author store
* @param topicStore the topic store
* @param authorRefStore the authorRef store
* @param licenseStore the license store
* @param uploadService the upload service
*/
private void registerPublishedApi(JerseyEnvironment jersey,
PkgStore pkgStore, AuthorStore authorStore, TopicStore topicStore,
LicenseStore licenseStore, SubmitService submitService) {
jersey.register(new JsonAuthorResource(authorStore));
jersey.register(new XmlAuthorResource(authorStore));
jersey.register(new JsonLicenseResource(licenseStore));
jersey.register(new XmlLicenseResource(licenseStore));
jersey.register(new JsonPkgResource(pkgStore));
jersey.register(new XmlPkgResource(pkgStore));
jersey.register(new JsonTopicResource(topicStore));
jersey.register(new XmlTopicResource(topicStore));
jersey.register(new JsonVersionResource());
jersey.register(new XmlVersionResource());
jersey.register(new XmlDtdResource());
jersey.register(new SubmitResource(submitService));
}
/**
* The method <code>registerResources</code> provides means to add resources
* to the registry.
*
* @param jersey the target registry
* @param adminEnvironment the administration environment
* @param config the configuration
* @return the user store
*
* @throws IOException in case of an I/O error
* @throws FileNotFoundException in case of an error
*/
private UserStore registerResources(JerseyEnvironment jersey,
AdminEnvironment admin,
CtanConfiguration config)
throws FileNotFoundException,
IOException {
// TODO jersey.register(new
// AuthValueFactoryProvider.Binder<>(User.class));
log.info("Registering REST resources");
IndexingService indexingService =
new IndexingService(config.getIndex());
IndexingSession indexingSession = indexingService.indexingSession();
SessionFactory dbSessionFactory =
CtanDatabaseBundles.SITE_DB_BUNDLE.getSessionFactory();
PkgStore pkgStore =
new PkgStore(dbSessionFactory, indexingSession);
AuthorStore authorStore =
new AuthorStore(dbSessionFactory, indexingSession);
TopicStore topicStore =
new TopicStore(dbSessionFactory, indexingSession);
MessageStore messageStore = new MessageStore(dbSessionFactory);
UserStore userStore = new UserStore(dbSessionFactory);
jersey.register(new CtanSite3Resource(config,
messageStore,
authorStore,
pkgStore,
topicStore));
jersey.register(new Message3Resource(messageStore));
var ctanConfig = config.getCtan();
var contentService =
new ContentService(config.getContent(), ctanConfig);
var pkgService = new PkgService(config, contentService, pkgStore);
var texArchiveNotesStore = new TexArchiveNotesStore(dbSessionFactory);
var texarchiveService = new TexArchiveService(config,
pkgService,
texArchiveNotesStore);
jersey.register(
new Texarchive3Resource(texarchiveService));
jersey.register(new TexArchiveNotes3Resource(texArchiveNotesStore));
jersey.register(new Content3Resource(contentService,
new LionService(config.getContent())));
var uploadConfig = config.getUpload();
var uploadStore = new UploadStore(dbSessionFactory);
var uploadService = new UploadService(uploadConfig, uploadStore);
jersey.register(new Upload3Resource(
uploadService));
jersey.register(new Author3Resource(ctanConfig,
authorStore,
userStore));
var bibtexService = new BibtexService(pkgStore);
var postingService =
new PostingsService(config.getCtanAnnounce().getDirectory());
jersey.register(new Pkg3Resource(ctanConfig,
pkgStore,
contentService,
bibtexService,
postingService,
new PkgAliasStore(dbSessionFactory)));
var voteStore = new VoteStore(dbSessionFactory);
jersey.register(new Topic3Resource(ctanConfig,
topicStore,
contentService,
voteStore));
jersey.register(new Vote3Resource(voteStore, new VoteService(voteStore),
pkgStore, userStore));
var mailService = new MailService(config.getMail());
var licenseStore = new LicenseStore(dbSessionFactory,
indexingService.indexingSession());
var submitService = new Submit11Service(config,
uploadService,
mailService,
pkgStore,
topicStore,
licenseStore);
registerPublishedApi(jersey,
pkgStore,
authorStore,
topicStore,
licenseStore,
submitService);
jersey.register(new MirrorRegistration3Resource(
new MirrorRegistrationService(
new MirrorRegistrationStore(dbSessionFactory),
mailService)));
GuestBookStore guestbookStore =
new GuestBookStore(dbSessionFactory, indexingSession);
jersey.register(
new GuestBook3Resource(guestbookStore));
jersey.register(new Version3Resource());
jersey.register(new JsonVersionResource());
var mirrorsSessions =
CtanDatabaseBundles.MIRRORS_DB_BUNDLE.getSessionFactory();
jersey.register(new Mirrors3Resource(
new MirrorService(
new MirrorStore(mirrorsSessions, indexingSession))));
jersey.register(new MirrMon3Resource(
new MirrMonService(config.getMirrmon())));
jersey.register(new License3Resource(licenseStore,
contentService));
jersey.register(new JsonLicenseResource(licenseStore));
jersey.register(new Lug3Resource(
new LugStore(dbSessionFactory)));
var ticketStore = new TicketStore(dbSessionFactory);
jersey.register(
new Ticket3Resource(ticketStore));
jersey.register(new User3Resource(new AccountService(
userStore,
ticketStore,
authorStore,
mailService)));
jersey.register(
new Role3Resource());
SearchService searchService = new SearchService(config.getIndex());
jersey.register(
new SearchResource(searchService));
jersey.register(
new Search3Resource(searchService));
jersey.register(
new CrudUser3Resource(userStore));
jersey.register(
new CrudAuthor3Resource(authorStore));
jersey.register(
new CrudGuestbook3Resource(guestbookStore));
jersey.register(
new CrudLicense3Resource(licenseStore));
jersey.register(
new CrudUpload3Resource(uploadStore));
jersey.register(new UserStopword3Resource(
new UserStopwordStore(dbSessionFactory)));
jersey.register(new Stopword3Resource(
new StopwordStore(dbSessionFactory)));
jersey.register(new CrudVote3Resource(
voteStore));
jersey.register(new Sitemap3Resource(new SitemapService()));
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
postingService.update(); // TODO start thread instead
jersey.register(
new Postings3Resource(postingService, contentService, pkgStore));
jersey.register(
new Rss20Resource(postingService, config));
jersey.register(
new Atom10Resource(postingService, config));
jersey.register(new Bibtex3Resource(
new BibtexService(pkgStore)));
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var catalogUpdateService =
new CatalogueImportService(config.getCatalogue(),
authorStore,
topicStore,
pkgStore,
licenseStore,
indexingService);
admin.addTask(new CatalogueUpdateTask(catalogUpdateService));
admin.addTask(new ArchiveFilesUpdateTask(
new ArchiveFilesUpdateService(config,
new ArchiveFileStore(config.getTexArchive(), dbSessionFactory,
indexingSession))));
return userStore;
}
/**
* {@inheritDoc}
*
* @see io.dropwizard.core.Application#run(io.dropwizard.core.Configuration,
* io.dropwizard.core.setup.Environment)
*/
@Override
public void run(@NonNull CtanConfiguration config, @NonNull Environment env)
throws Exception {
UserStore userStore = registerResources(env.jersey(),
env.admin(),
config);
registerHealthChecks(env.healthChecks(), config);
configureAuth(env.jersey(), userStore);
configureCors(env);
}
}