/* * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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.amazonaws.mobileconnectors.kinesis.kinesisrecorder; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.kinesisfirehose.AmazonKinesisFirehose; import com.amazonaws.services.kinesisfirehose.AmazonKinesisFirehoseClient; import com.amazonaws.util.VersionInfoUtils; import java.io.File; import java.util.regex.Pattern; /** * The {@link KinesisFirehoseRecorder} is a high level client for Amazon Kinesis * Firehose. It can cache data on users' Android device and send them in a batch * later. * <p/> * Note: {@link KinesisFirehoseRecorder#submitAllRecords()} is a synchronous * method that sends cached data to Amazon Kinesis over the network. Therefore * you should not call KinesisRecorder methods on the main thread. * <p/> * Warning: You should not create multiple {@link KinesisFirehoseRecorder} given * the same directory. Doing so is an error and behavior is undefined. * <p/> * Note: {@link KinesisFirehoseRecorder} stores the requests in plain-text, and * does not perform additional security measures outside of what the Android OS * offers by default. Therefore it is recommended you pass a directory that is * only visible to your application, and additionally do not store highly * sensitive information using {@link KinesisFirehoseRecorder}. * * <pre> * // working directory for the recorder * File directory = context.getCachedDir(); * // AWS Firehose region * Regions region = Regions.US_WEST_2; * // initialize a credentials provider * AWSCredentialsProvider provider = new CognitoCachingCredentialsProvider( * context, * "identityPoolId", * Regions.US_EAST_1); * KinesisFirehoseRecorder firehoseRecorder = new KinesisFirehoseRecorder( * directory, region, provider); * // save some strings * String streamName = "my_stream"; // Firehose delivery stream name * firehoseRecorder.saveRecord("Hello world!\n", streamName); * firehoseRecorder.saveRecord("Streaming data to S3 via Firehose is easy.\n", streamName); * * // send previously save data to Amazon Firehose * // Note: submitAllRecords() makes network calls, so wrap it in an AsyncTask. * new AsyncTask<Void, Void, Void>() { * @Override * protected Void doInBackground(Void... v) { * try { * firehoseRecorder.submitAllRecords(); * } catch (AmazonClientException ace) { * // error occurs. * } * } * }.execute(); * </pre> * * {@link KinesisFirehoseRecorder} requires an IAM policy that allows * PutRecordBatch action on the target delivery stream. Here is an example: * * <pre> * { * "Version": "2012-10-17", * "Statement": [{ * "Effect": "Allow", * "Action": [ "firehose:PutRecordBatch" ], * "Resource": [ * "arn:aws:firehose:us-east-1:123456789012:deliverystream/my_stream" * ] * }] * } * </pre> */ public class KinesisFirehoseRecorder extends AbstractKinesisRecorder { /** * Name of local file record store. */ private static final String RECORD_FILE_NAME = "kinesis_firehose_records"; /** * User agent string to identify {@link KinesisFirehoseRecorder}. */ private static final String USER_AGENT = KinesisFirehoseRecorder.class.getName() + "/" + VersionInfoUtils.getVersion(); /** * The maximum size of a record sent to Firehose, before base64-encoding, is * 1000 KB. */ private static final int MAX_RECORD_SIZE_BYTES = 1000 * 1024; /** * Valid stream name pattern. */ private static final Pattern STREAM_NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_.-]{1,64}"); private FirehoseRecordSender sender; /** * Constructs a new Kinesis Firehose Recorder specifying a directory that * the recorder has exclusive access to for storing requests. * <p> * Note: Kinesis Firehose Recorder is synchronous, and it's methods should * not be called on the main thread. * <p> * Note: Kinesis Firehose Recorder stores requests in plain-text, we * recommend using a directory that is only readable by your application and * not storing highly sensitive information in requests stored by Kinesis * Firehose Recorder. * * @param directory A directory {@link KinesisFirehoseRecorder} can use for * storing requests. * @param region The region of Amazon Kinesis Firehose this Recorder should * save and send requests to. * @param credentialsProvider The credentials provider to use when making * requests to AWS */ public KinesisFirehoseRecorder(File directory, Regions region, AWSCredentialsProvider credentialsProvider) { this(directory, region, credentialsProvider, new KinesisRecorderConfig()); } /** * Constructs a new Kinesis Firehose Recorder specifying a directory that * the recorder has exclusive access to for storing requests. * <p> * Note: Kinesis Firehose Recorder is synchronous, and it's methods should * not be called on the main thread. * <p> * Note: Kinesis Firehose Recorder stores requests in plain-text, we * recommend using a directory that is only readable by your application and * not storing highly sensitive information in requests stored by Kinesis * Firehose Recorder. * * @param directory A directory {@link KinesisFirehoseRecorder} can use for * storing requests. * @param region The region of Amazon Kinesis Firehose this Recorder should * save and send requests to. * @param credentialsProvider The credentials provider to use when making * requests to AWS * @param config Allows configuring various parameters of the recorder */ public KinesisFirehoseRecorder(File directory, Regions region, AWSCredentialsProvider credentialsProvider, KinesisRecorderConfig config) { super(new FileRecordStore(directory, RECORD_FILE_NAME, config.getMaxStorageSize()), config); AmazonKinesisFirehose client = new AmazonKinesisFirehoseClient(credentialsProvider, config.getClientConfiguration()); client.setRegion(Region.getRegion(region)); sender = new FirehoseRecordSender(client, USER_AGENT); } /** * Constructs a {@link KinesisFirehoseRecorder}. It allows you to inject * dependencies. * * @param sender a {@link FirehoseRecordSender} * @param recordStore record store * @param config configuration */ KinesisFirehoseRecorder(FirehoseRecordSender sender, FileRecordStore recordStore, KinesisRecorderConfig config) { super(recordStore, config); this.sender = sender; } @Override protected RecordSender getRecordSender() { return sender; } @Override public void saveRecord(byte[] data, String streamName) { if (streamName == null || !STREAM_NAME_PATTERN.matcher(streamName).matches()) { throw new IllegalArgumentException("Invalid stream name: " + streamName); } if (data == null || data.length == 0 || data.length > MAX_RECORD_SIZE_BYTES) { throw new IllegalArgumentException("Invalid data size."); } super.saveRecord(data, streamName); } }