com.amazonaws.services.kinesis.clientlibrary.config.KinesisClientLibConfigurator.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.services.kinesis.clientlibrary.config.KinesisClientLibConfigurator.java

Source

/*
 * Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Amazon Software License (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/asl/
 *
 * 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.services.kinesis.clientlibrary.config;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.KinesisClientLibConfiguration;

/**
 * KinesisClientLibConfigurator constructs a KinesisClientLibConfiguration from java properties file. The following
 * three properties must be provided. 1) "applicationName" 2) "streamName" 3) "AWSCredentialsProvider"
 * KinesisClientLibConfigurator will help to automatically assign the value of "workerId" if this property is not
 * provided. In the specified properties file, any properties, which matches the variable name in
 * KinesisClientLibConfiguration and has a corresponding "with{variableName}" setter method, will be read in, and its
 * value in properties file will be assigned to corresponding variable in KinesisClientLibConfiguration.
 */
public class KinesisClientLibConfigurator {

    private static final Log LOG = LogFactory.getLog(KinesisClientLibConfigurator.class);
    private static final String PREFIX = "with";

    // Required properties
    private static final String PROP_APP_NAME = "applicationName";
    private static final String PROP_STREAM_NAME = "streamName";
    private static final String PROP_CREDENTIALS_PROVIDER = "AWSCredentialsProvider";
    private static final String PROP_WORKER_ID = "workerId";

    private Map<Class<?>, IPropertyValueDecoder<?>> classToDecoder;
    private Map<String, List<Method>> nameToMethods;

    /**
     * Constructor.
     */
    public KinesisClientLibConfigurator() {
        List<IPropertyValueDecoder<? extends Object>> getters = Arrays.asList(new IntegerPropertyValueDecoder(),
                new LongPropertyValueDecoder(), new BooleanPropertyValueDecoder(),
                new AWSCredentialsProviderPropertyValueDecoder(), new StringPropertyValueDecoder(),
                new InitialPositionInStreamPropertyValueDecoder(), new ClientConfigurationPropertyValueDecoder(),
                new SetPropertyValueDecoder());

        classToDecoder = new Hashtable<Class<?>, IPropertyValueDecoder<?>>();
        for (IPropertyValueDecoder<?> getter : getters) {
            for (Class<?> clazz : getter.getSupportedTypes()) {
                /*
                 * We could validate that we never overwrite a getter but we can also do this by manual inspection of
                 * the getters.
                 */
                classToDecoder.put(clazz, getter);
            }
        }
        nameToMethods = new Hashtable<String, List<Method>>();
        for (Method method : KinesisClientLibConfiguration.class.getMethods()) {
            if (!nameToMethods.containsKey(method.getName())) {
                nameToMethods.put(method.getName(), new ArrayList<Method>());
            }
            nameToMethods.get(method.getName()).add(method);
        }
    }

    /**
     * Return a KinesisClientLibConfiguration with variables configured as specified by the properties in config stream.
     * Program will fail immediately, if customer provide: 1) invalid variable value. Program will log it as warning and
     * continue, if customer provide: 1) variable with unsupported variable type. 2) a variable with name which does not
     * match any of the variables in KinesisClientLibConfigration.
     * 
     * @param properties a Properties object containing the configuration information
     * @return KinesisClientLibConfiguration
     */
    public KinesisClientLibConfiguration getConfiguration(Properties properties) {
        // The three minimum required arguments for constructor are obtained first. They are all mandatory, all of them
        // should be provided. If any of these three failed to be set, program will fail.
        IPropertyValueDecoder<String> stringValueDecoder = new StringPropertyValueDecoder();
        IPropertyValueDecoder<AWSCredentialsProvider> awsCPPropGetter = new AWSCredentialsProviderPropertyValueDecoder();
        String applicationName = stringValueDecoder.decodeValue(properties.getProperty(PROP_APP_NAME));
        String streamName = stringValueDecoder.decodeValue(properties.getProperty(PROP_STREAM_NAME));
        AWSCredentialsProvider provider = awsCPPropGetter
                .decodeValue(properties.getProperty(PROP_CREDENTIALS_PROVIDER));

        if (applicationName == null || applicationName.isEmpty()) {
            throw new IllegalArgumentException("Value of applicationName should be explicitly provided.");
        }
        if (streamName == null || streamName.isEmpty()) {
            throw new IllegalArgumentException("Value of streamName should be explicitly provided.");
        }

        // Allow customer not to provide workerId or to provide empty worker id.
        String workerId = stringValueDecoder.decodeValue(properties.getProperty(PROP_WORKER_ID));
        if (workerId == null || workerId.isEmpty()) {
            workerId = UUID.randomUUID().toString();
            LOG.info("Value of workerId is not provided in the properties. WorkerId is automatically "
                    + "assigned as: " + workerId);
        }

        KinesisClientLibConfiguration config = new KinesisClientLibConfiguration(applicationName, streamName,
                provider, workerId);

        Set<String> requiredNames = new HashSet<String>(
                Arrays.asList(PROP_STREAM_NAME, PROP_APP_NAME, PROP_WORKER_ID, PROP_CREDENTIALS_PROVIDER));

        // Set all the variables that are not used for constructor.
        for (Object keyObject : properties.keySet()) {
            String key = keyObject.toString();
            if (!requiredNames.contains(key)) {
                withProperty(key, properties, config);
            }
        }

        return config;
    }

    /**
     * @param configStream the input stream containing the configuration information
     * @return KinesisClientLibConfiguration
     */
    public KinesisClientLibConfiguration getConfiguration(InputStream configStream) {
        Properties properties = new Properties();
        try {
            properties.load(configStream);
        } catch (IOException e) {
            String msg = "Could not load properties from the stream provided";
            throw new IllegalStateException(msg, e);
        } finally {
            try {
                configStream.close();
            } catch (IOException e) {
                String msg = "Encountered error while trying to close properties file.";
                throw new IllegalStateException(msg, e);
            }
        }
        return getConfiguration(properties);
    }

    private void withProperty(String propertyKey, Properties properties, KinesisClientLibConfiguration config) {
        if (propertyKey.isEmpty()) {
            throw new IllegalArgumentException("The property can't be empty string");
        }
        // Assume that all the setters in KinesisClientLibConfiguration are in the following format
        // They all start with "with" followed by the variable name with first letter capitalized
        String targetMethodName = PREFIX + Character.toUpperCase(propertyKey.charAt(0)) + propertyKey.substring(1);
        String propertyValue = properties.getProperty(propertyKey);
        if (nameToMethods.containsKey(targetMethodName)) {
            for (Method method : nameToMethods.get(targetMethodName)) {
                if (method.getParameterTypes().length == 1 && method.getName().equals(targetMethodName)) {
                    Class<?> paramType = method.getParameterTypes()[0];
                    if (classToDecoder.containsKey(paramType)) {
                        IPropertyValueDecoder<?> decoder = classToDecoder.get(paramType);
                        try {
                            method.invoke(config, decoder.decodeValue(propertyValue));
                            LOG.info(String.format("Successfully set property %s with value %s", propertyKey,
                                    propertyValue));
                            return;
                        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                            // At this point, we really thought that we could call this method.
                            LOG.warn(String.format("Encountered an error while invoking method %s with value %s. "
                                    + "Exception was %s", method, propertyValue, e));
                        } catch (UnsupportedOperationException e) {
                            LOG.warn(String.format("The property %s is not supported as type %s at this time.",
                                    propertyKey, paramType));
                        }
                    } else {
                        LOG.debug(
                                String.format("No method for decoding parameters of type %s so method %s could not "
                                        + "be invoked.", paramType, method));
                    }
                } else {
                    LOG.debug(
                            String.format(
                                    "Method %s doesn't look like it is appropriate for setting property %s. "
                                            + "Looking for something called %s.",
                                    method, propertyKey, targetMethodName));
                }
            }
        } else {
            LOG.debug(
                    String.format("There was no appropriately named method for setting property %s.", propertyKey));
        }
    }
}