All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.symphony.oss.fugue.aws.sns.SnsPublisherBase Maven / Gradle / Ivy

There is a newer version: 0.3.0
Show newest version
/*
 *
 *
 * Copyright 2018 Symphony Communication Services, LLC.
 *
 * Licensed to The Symphony Software Foundation (SSF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.symphony.oss.fugue.aws.sns;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import com.amazonaws.services.sns.model.MessageAttributeValue;
import com.amazonaws.services.sns.model.PublishRequest;
import com.google.common.collect.ImmutableList;
import com.symphony.oss.commons.fault.FaultAccumulator;
import com.symphony.oss.commons.type.provider.IIntegerProvider;
import com.symphony.oss.fugue.config.IConfiguration;
import com.symphony.oss.fugue.naming.TopicName;
import com.symphony.oss.fugue.pubsub.AbstractPublisherManager;
import com.symphony.oss.fugue.pubsub.IPubSubMessage;
import com.symphony.oss.fugue.pubsub.IPublisher;
import com.symphony.oss.fugue.trace.ITraceContext;

/**
 * Amazon SNS implementation of PublisherManager.
 * 
 * @author Bruce Skingle
 *
 * @param  Type of concrete manager, needed for fluent methods.
 *
 */
public abstract class SnsPublisherBase> extends AbstractPublisherManager
{
  private static final Logger                  log_              = LoggerFactory.getLogger(SnsPublisherBase.class);

  protected static final int                   MAX_MESSAGE_SIZE  = 256 * 1024; // 256K
  protected static final int                   BILLABLE_MESSAGE_SIZE  = 64 * 1024;

  protected final Map publisherNameMap_ = new HashMap<>();
  protected final List           publishers_       = new ArrayList<>();

  protected final String                       region_;
  protected final String                       accountId_;
  protected final AmazonSNS                    snsClient_;

  protected final ImmutableList       subscriberAccountIds_;

  
  protected SnsPublisherBase(Class type, Builder builder)
  {
    super(type, builder);
    
    region_    = builder.region_;
    accountId_ = builder.accountId_;
    snsClient_ = builder.snsBuilder_.build();
    
    subscriberAccountIds_ =  ImmutableList.copyOf(
        builder.config_.getConfiguration("amazon").getListOfString("subscriberAccountIds", new ArrayList<>()));
    
    int errorCnt = 0;
    
    for(TopicName topicName : builder.topicNames_)
    {
      if(!validateTopic(topicName))
        errorCnt++;
    }
    
    if(errorCnt > 0)
    {
      throw new IllegalStateException("There are " + errorCnt + " topic validation errors");
    }
    
    for(TopicName topicName : builder.topicNames_)
    {
      publisherNameMap_.put(topicName, new SnsPublisher(topicName, getTopicARN(topicName), this));
    }
  }

  /**
   * Builder.
   * 
   * @author Bruce Skingle
   *
   * @param    The concrete type returned by fluent methods.
   * @param    The concrete type of the built object.
   */
  public static abstract class Builder, B extends AbstractPublisherManager>
  extends AbstractPublisherManager.Builder
  {
    protected final AmazonSNSClientBuilder snsBuilder_;
    protected final Set         topicNames_ = new HashSet<>();

    protected IConfiguration               config_;
    protected String                       region_;
    protected String                       accountId_;
    
    protected Builder(Class type)
    {
      super(type);
      
      snsBuilder_ = AmazonSNSClientBuilder.standard();
    }
    
    /**
     * Set the global application configuration.
     * 
     * @param config The global application configuration.
     * 
     * @return this (fluent method)
     */
    public T withConfig(IConfiguration config)
    {
      config_ = config;
      
      return self();
    }
    
    /**
     * Set the AWS region.
     * 
     * @param region The AWS region in which to operate.
     * 
     * @return this (fluent method)
     */
    public T withRegion(String region)
    {
      region_ = region;
      
      snsBuilder_.withRegion(region_);
      
      return self();
    }
    
    /**
     * Set the AWS account ID.
     * 
     * @param accountId The ID of the AWS account in which to operate.
     * 
     * @return this (fluent method)
     */
    public T withAccountId(String accountId)
    {
      accountId_  = accountId;
      
      return self();
    }
    
    /**
     * Set the AWS credentials provider.
     * 
     * @param credentialsProvider An AWS credentials provider.
     * 
     * @return this (fluent method)
     */
    public T withCredentials(AWSCredentialsProvider credentialsProvider)
    {
      snsBuilder_.withCredentials(credentialsProvider);
      
      return self();
    }
    
    @Override
    public T withTopic(TopicName name)
    {
      topicNames_.add(name);
      
      return self();
    }

    @Override
    public synchronized void validate(FaultAccumulator faultAccumulator)
    {
      super.validate(faultAccumulator);
      
      faultAccumulator.checkNotNull(config_,    "config");
      faultAccumulator.checkNotNull(region_,    "region");
      faultAccumulator.checkNotNull(accountId_, "accountId");
      
      IConfiguration      snsConfig     = config_.getConfiguration("org/symphonyoss/s2/fugue/aws/sns");
      ClientConfiguration clientConfig  = new ClientConfiguration()
          .withMaxConnections(          snsConfig.getInt( "maxConnections",           ClientConfiguration.DEFAULT_MAX_CONNECTIONS))
          .withClientExecutionTimeout(  snsConfig.getInt( "clientExecutionTimeout",   ClientConfiguration.DEFAULT_CLIENT_EXECUTION_TIMEOUT))
          .withConnectionMaxIdleMillis( snsConfig.getLong("connectionMaxIdleMillis",  ClientConfiguration.DEFAULT_CONNECTION_MAX_IDLE_MILLIS))
          .withConnectionTimeout(       snsConfig.getInt( "connectionTimeout",        ClientConfiguration.DEFAULT_CONNECTION_TIMEOUT))
          ;
      
      log_.info("Starting SNSPublisherManager in " + region_ + " with " + clientConfig.getMaxConnections() + " max connections...");

      snsBuilder_
          .withClientConfiguration(clientConfig)
          ;
    }
  }

//  /**
//   * Constructor.
//   * 
//   * @param config      The configuration provider.
//   * @param nameFactory A name factory.
//   * @param region      The AWS region to use.
//   * @param accountId   The AWS numeric account ID 
//   */
//  public SnsPublisherManager(IConfiguration config, INameFactory nameFactory, String region, String accountId)
//  {
//    this(config, nameFactory, region, accountId, null, false);
//  }
//  
//  /**
//   * Constructor.
//   * 
//   * @param config      The configuration provider.
//   * @param nameFactory A name factory.
//   * @param region      The AWS region to use.
//   * @param accountId   The AWS numeric account ID 
//   * @param credentials AWS credentials.
//   */
//  public SnsPublisherManager(IConfiguration config, INameFactory nameFactory, String region, String accountId, AWSCredentialsProvider credentials)
//  {
//    this(config, nameFactory, region, accountId, credentials, false);
//  }
//  
//  protected SnsPublisherManager(IConfiguration config, INameFactory nameFactory, String region, String accountId, AWSCredentialsProvider credentials, boolean initialize)
//  {
//    super(nameFactory, SnsPublisherManager.class);
//    
//    region_ = region;
//    accountId_ = accountId;
//    initialize_ = initialize;
//    
//    IConfiguration snsConfig = config.getConfiguration("org/symphonyoss/s2/fugue/aws/sns");
//    
//    ClientConfiguration clientConfig = new ClientConfiguration()
//        .withMaxConnections(          snsConfig.getInt( "maxConnections",           ClientConfiguration.DEFAULT_MAX_CONNECTIONS))
//        .withClientExecutionTimeout(  snsConfig.getInt( "clientExecutionTimeout",   ClientConfiguration.DEFAULT_CLIENT_EXECUTION_TIMEOUT))
//        .withConnectionMaxIdleMillis( snsConfig.getLong("connectionMaxIdleMillis",  ClientConfiguration.DEFAULT_CONNECTION_MAX_IDLE_MILLIS))
//        .withConnectionTimeout(       snsConfig.getInt( "connectionTimeout",        ClientConfiguration.DEFAULT_CONNECTION_TIMEOUT))
//        ;
//    
//    log_.info("Starting SNSPublisherManager in " + region_ + " with " + clientConfig.getMaxConnections() + " max connections...");
//    
//    AmazonSNSClientBuilder builder = AmazonSNSClientBuilder.standard()
//        .withRegion(region_)
//        .withClientConfiguration(clientConfig);
//    
//    if(credentials != null)
//    {
//      builder.withCredentials(credentials);
//    }
//    
//    snsClient_ = builder.build();
//    
//  }


  /**
   * Topic-arns can be constructed if the region, accountId, and topic name is known.
   * 
   * $topicArn = 'arn:aws:sns:REGION:ACCOUNT-ID:TOPIC-NAME'
   *
   * @param topicName - name of topic
   * 
   * @return The topic ARN
   */
  public String getTopicARN(TopicName topicName)
  {
    return "arn:aws:sns:" + region_ + ":" + accountId_ + ":" + topicName;
  }

  @Override
  public void start()
  {
  }

  /**
   * Validate the given topic name.
   * 
   * @param topicName The name of a topic.
   * 
   * @return true if the topic is valid.
   */
  protected abstract boolean validateTopic(TopicName topicName);

  @Override
  public void stop()
  {
    snsClient_.shutdown();
    
    for(SnsPublisher publisher : publishers_)
    {
      publisher.close();
    }
  }

  @Override
  public synchronized IPublisher getPublisherByName(TopicName topicName)
  {
    SnsPublisher publisher = publisherNameMap_.get(topicName);
    
    if(publisher == null)
    {
      throw new IllegalArgumentException("Unregistered topic \"" + topicName + "\"");
    }
    
    return publisher;
  }
  
  protected void send(String topicName, String topicArn, IPubSubMessage pubSubMessage, ITraceContext trace)
  {
    trace.trace("ABOUT-TO-PUBLISH", "SNS_TOPIC", topicName);

    PublishRequest publishRequest = new PublishRequest(topicArn, pubSubMessage.getPayload());
    
    if(!pubSubMessage.getAttributes().isEmpty())
    {
      Map messageAttributes = new HashMap<>();
      
      for(Entry entry : pubSubMessage.getAttributes().entrySet())
      {
        messageAttributes.put(entry.getKey(), getAttribute(entry.getValue()));
      }
      
      publishRequest.withMessageAttributes(messageAttributes);
    }
    
    snsClient_.publish(publishRequest);
    trace.trace("PUBLISHED", "SNS_TOPIC", topicName);
  }

  private static MessageAttributeValue getAttribute(Object value)
  {
    if(value instanceof Number || value instanceof IIntegerProvider)
    {
      return new MessageAttributeValue()
          .withDataType("Number")
          .withStringValue(value.toString());
    }
    
    return new MessageAttributeValue()
        .withDataType("String")
        .withStringValue(value.toString());
    
  }

  @Override
  public int getMaximumMessageSize()
  {
    return MAX_MESSAGE_SIZE;
  }
}