org.whispersystems.textsecuregcm.WhisperServerService.java Source code

Java tutorial

Introduction

Here is the source code for org.whispersystems.textsecuregcm.WhisperServerService.java

Source

/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.whispersystems.textsecuregcm;

import com.codahale.metrics.SharedMetricRegistries;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.google.common.base.Optional;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.jersey.client.ClientProperties;
import org.skife.jdbi.v2.DBI;
import org.whispersystems.dispatch.DispatchChannel;
import org.whispersystems.dispatch.DispatchManager;
import org.whispersystems.dropwizard.simpleauth.AuthDynamicFeature;
import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider;
import org.whispersystems.dropwizard.simpleauth.BasicCredentialAuthFilter;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.AttachmentController;
import org.whispersystems.textsecuregcm.controllers.DeviceController;
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
import org.whispersystems.textsecuregcm.controllers.FederationControllerV1;
import org.whispersystems.textsecuregcm.controllers.FederationControllerV2;
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
import org.whispersystems.textsecuregcm.controllers.KeysControllerV1;
import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.controllers.ProvisioningController;
import org.whispersystems.textsecuregcm.controllers.ReceiptController;
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.liquibase.NameableMigrationsBundle;
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge;
import org.whispersystems.textsecuregcm.metrics.FileDescriptorGauge;
import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge;
import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge;
import org.whispersystems.textsecuregcm.metrics.NetworkSentGauge;
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
import org.whispersystems.textsecuregcm.providers.RedisHealthCheck;
import org.whispersystems.textsecuregcm.providers.TimeProvider;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
import org.whispersystems.textsecuregcm.push.FeedbackHandler;
import org.whispersystems.textsecuregcm.push.PushSender;
import org.whispersystems.textsecuregcm.push.PushServiceClient;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.push.WebsocketSender;
import org.whispersystems.textsecuregcm.sms.SmsSender;
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Accounts;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
import org.whispersystems.textsecuregcm.storage.Keys;
import org.whispersystems.textsecuregcm.storage.Messages;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.storage.PendingAccounts;
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
import org.whispersystems.textsecuregcm.storage.PendingDevices;
import org.whispersystems.textsecuregcm.storage.PendingDevicesManager;
import org.whispersystems.textsecuregcm.storage.PubSubManager;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.UrlSigner;
import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener;
import org.whispersystems.textsecuregcm.websocket.DeadLetterHandler;
import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
import org.whispersystems.textsecuregcm.workers.DirectoryCommand;
import org.whispersystems.textsecuregcm.workers.PeriodicStatsCommand;
import org.whispersystems.textsecuregcm.workers.TrimMessagesCommand;
import org.whispersystems.textsecuregcm.workers.VacuumCommand;
import org.whispersystems.websocket.WebSocketResourceProviderFactory;
import org.whispersystems.websocket.setup.WebSocketEnvironment;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletRegistration;
import javax.ws.rs.client.Client;
import java.security.Security;
import java.util.EnumSet;

import static com.codahale.metrics.MetricRegistry.name;
import io.dropwizard.Application;
import io.dropwizard.client.JerseyClientBuilder;
import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.jdbi.DBIFactory;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import redis.clients.jedis.JedisPool;

public class WhisperServerService extends Application<WhisperServerConfiguration> {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    @Override
    public void initialize(Bootstrap<WhisperServerConfiguration> bootstrap) {
        bootstrap.addCommand(new DirectoryCommand());
        bootstrap.addCommand(new VacuumCommand());
        bootstrap.addCommand(new TrimMessagesCommand());
        bootstrap.addCommand(new PeriodicStatsCommand());
        bootstrap.addCommand(new DeleteUserCommand());
        bootstrap
                .addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("accountdb", "accountsdb.xml") {
                    @Override
                    public DataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) {
                        return configuration.getDataSourceFactory();
                    }
                });

        bootstrap.addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("messagedb", "messagedb.xml") {
            @Override
            public DataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) {
                return configuration.getMessageStoreConfiguration();
            }
        });
    }

    @Override
    public String getName() {
        return "whisper-server";
    }

    @Override
    public void run(WhisperServerConfiguration config, Environment environment) throws Exception {
        SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics());
        environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        environment.getObjectMapper().setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        environment.getObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

        DBIFactory dbiFactory = new DBIFactory();
        DBI database = dbiFactory.build(environment, config.getDataSourceFactory(), "accountdb");
        DBI messagedb = dbiFactory.build(environment, config.getMessageStoreConfiguration(), "messagedb");

        Accounts accounts = database.onDemand(Accounts.class);
        PendingAccounts pendingAccounts = database.onDemand(PendingAccounts.class);
        PendingDevices pendingDevices = database.onDemand(PendingDevices.class);
        Keys keys = database.onDemand(Keys.class);
        Messages messages = messagedb.onDemand(Messages.class);

        RedisClientFactory cacheClientFactory = new RedisClientFactory(config.getCacheConfiguration().getUrl());
        JedisPool cacheClient = cacheClientFactory.getRedisClientPool();
        JedisPool directoryClient = new RedisClientFactory(config.getDirectoryConfiguration().getUrl())
                .getRedisClientPool();
        Client httpClient = initializeHttpClient(environment, config);

        DirectoryManager directory = new DirectoryManager(directoryClient);
        PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, cacheClient);
        PendingDevicesManager pendingDevicesManager = new PendingDevicesManager(pendingDevices, cacheClient);
        AccountsManager accountsManager = new AccountsManager(accounts, directory, cacheClient);
        FederatedClientManager federatedClientManager = new FederatedClientManager(environment,
                config.getJerseyClientConfiguration(), config.getFederationConfiguration());
        MessagesManager messagesManager = new MessagesManager(messages);
        DeadLetterHandler deadLetterHandler = new DeadLetterHandler(messagesManager);
        DispatchManager dispatchManager = new DispatchManager(cacheClientFactory,
                Optional.<DispatchChannel>of(deadLetterHandler));
        PubSubManager pubSubManager = new PubSubManager(cacheClient, dispatchManager);
        PushServiceClient pushServiceClient = new PushServiceClient(httpClient, config.getPushConfiguration());
        WebsocketSender websocketSender = new WebsocketSender(messagesManager, pubSubManager);
        AccountAuthenticator deviceAuthenticator = new AccountAuthenticator(accountsManager);
        FederatedPeerAuthenticator federatedPeerAuthenticator = new FederatedPeerAuthenticator(
                config.getFederationConfiguration());
        RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), cacheClient);

        ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient, pubSubManager);
        TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration());
        SmsSender smsSender = new SmsSender(twilioSmsSender);
        UrlSigner urlSigner = new UrlSigner(config.getS3Configuration());
        PushSender pushSender = new PushSender(apnFallbackManager, pushServiceClient, websocketSender,
                config.getPushConfiguration().getQueueSize());
        ReceiptSender receiptSender = new ReceiptSender(accountsManager, pushSender, federatedClientManager);
        FeedbackHandler feedbackHandler = new FeedbackHandler(pushServiceClient, accountsManager);
        TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
        Optional<byte[]> authorizationKey = config.getRedphoneConfiguration().getAuthorizationKey();

        environment.lifecycle().manage(apnFallbackManager);
        environment.lifecycle().manage(pubSubManager);
        environment.lifecycle().manage(feedbackHandler);
        environment.lifecycle().manage(pushSender);

        AttachmentController attachmentController = new AttachmentController(rateLimiters, federatedClientManager,
                urlSigner);
        KeysControllerV1 keysControllerV1 = new KeysControllerV1(rateLimiters, keys, accountsManager,
                federatedClientManager);
        KeysControllerV2 keysControllerV2 = new KeysControllerV2(rateLimiters, keys, accountsManager,
                federatedClientManager);
        MessageController messageController = new MessageController(rateLimiters, pushSender, receiptSender,
                accountsManager, messagesManager, federatedClientManager);

        environment.jersey().register(new AuthDynamicFeature(
                new BasicCredentialAuthFilter.Builder<Account>().setAuthenticator(deviceAuthenticator)
                        .setPrincipal(Account.class).buildAuthFilter(),
                new BasicCredentialAuthFilter.Builder<FederatedPeer>().setAuthenticator(federatedPeerAuthenticator)
                        .setPrincipal(FederatedPeer.class).buildAuthFilter()));
        environment.jersey().register(new AuthValueFactoryProvider.Binder());

        environment.jersey()
                .register(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender,
                        messagesManager, new TimeProvider(), authorizationKey, turnTokenGenerator,
                        config.getTestDevices()));
        environment.jersey().register(
                new DeviceController(pendingDevicesManager, accountsManager, messagesManager, rateLimiters));
        environment.jersey().register(new DirectoryController(rateLimiters, directory));
        environment.jersey().register(new FederationControllerV1(accountsManager, attachmentController,
                messageController, keysControllerV1));
        environment.jersey().register(new FederationControllerV2(accountsManager, attachmentController,
                messageController, keysControllerV2));
        environment.jersey().register(new ReceiptController(receiptSender));
        environment.jersey().register(new ProvisioningController(rateLimiters, pushSender));
        environment.jersey().register(attachmentController);
        environment.jersey().register(keysControllerV1);
        environment.jersey().register(keysControllerV2);
        environment.jersey().register(messageController);

        if (config.getWebsocketConfiguration().isEnabled()) {
            WebSocketEnvironment webSocketEnvironment = new WebSocketEnvironment(environment, config, 90000);
            webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(deviceAuthenticator));
            webSocketEnvironment.setConnectListener(new AuthenticatedConnectListener(accountsManager, pushSender,
                    receiptSender, messagesManager, pubSubManager));
            webSocketEnvironment.jersey().register(new KeepAliveController(pubSubManager));
            webSocketEnvironment.jersey().register(messageController);

            WebSocketEnvironment provisioningEnvironment = new WebSocketEnvironment(environment, config);
            provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(pubSubManager));
            provisioningEnvironment.jersey().register(new KeepAliveController(pubSubManager));

            WebSocketResourceProviderFactory webSocketServlet = new WebSocketResourceProviderFactory(
                    webSocketEnvironment);
            WebSocketResourceProviderFactory provisioningServlet = new WebSocketResourceProviderFactory(
                    provisioningEnvironment);

            ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket",
                    webSocketServlet);
            ServletRegistration.Dynamic provisioning = environment.servlets().addServlet("Provisioning",
                    provisioningServlet);

            websocket.addMapping("/v1/websocket/");
            websocket.setAsyncSupported(true);

            provisioning.addMapping("/v1/websocket/provisioning/");
            provisioning.setAsyncSupported(true);

            webSocketServlet.start();
            provisioningServlet.start();

            FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
            filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
            filter.setInitParameter("allowedOrigins", "*");
            filter.setInitParameter("allowedHeaders",
                    "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin,X-Signal-Agent");
            filter.setInitParameter("allowedMethods", "GET,PUT,POST,DELETE,OPTIONS");
            filter.setInitParameter("preflightMaxAge", "5184000");
            filter.setInitParameter("allowCredentials", "true");
        }

        environment.healthChecks().register("directory", new RedisHealthCheck(directoryClient));
        environment.healthChecks().register("cache", new RedisHealthCheck(cacheClient));

        environment.jersey().register(new IOExceptionMapper());
        environment.jersey().register(new RateLimitExceededExceptionMapper());
        environment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
        environment.jersey().register(new DeviceLimitExceededExceptionMapper());

        environment.metrics().register(name(CpuUsageGauge.class, "cpu"), new CpuUsageGauge());
        environment.metrics().register(name(FreeMemoryGauge.class, "free_memory"), new FreeMemoryGauge());
        environment.metrics().register(name(NetworkSentGauge.class, "bytes_sent"), new NetworkSentGauge());
        environment.metrics().register(name(NetworkReceivedGauge.class, "bytes_received"),
                new NetworkReceivedGauge());
        environment.metrics().register(name(FileDescriptorGauge.class, "fd_count"), new FileDescriptorGauge());
    }

    private Client initializeHttpClient(Environment environment, WhisperServerConfiguration config) {
        Client httpClient = new JerseyClientBuilder(environment).using(config.getJerseyClientConfiguration())
                .build(getName());

        httpClient.property(ClientProperties.CONNECT_TIMEOUT, 1000);
        httpClient.property(ClientProperties.READ_TIMEOUT, 1000);

        return httpClient;
    }

    public static void main(String[] args) throws Exception {
        new WhisperServerService().run(args);
    }
}