<repo_name>Howww/url-shortener<file_sep>/src/main/java/org/santel/url/ServletInitializer.java
package org.santel.url;

import org.springframework.boot.builder.*;
import org.springframework.boot.context.web.*;

public class ServletInitializer extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		System.setProperty("spring.profiles.active", "production");
		return application.sources(ShorteningService.class, ShorteningService.ProductionShorteningConfiguration.class);
	}

}
<file_sep>/src/main/java/org/santel/url/dao/DynamoDbBroker.java
package org.santel.url.dao;

import com.amazonaws.regions.*;
import com.amazonaws.services.dynamodbv2.*;
import com.amazonaws.services.dynamodbv2.document.*;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.internal.*;
import com.amazonaws.services.dynamodbv2.document.spec.*;
import com.amazonaws.services.dynamodbv2.document.utils.*;
import com.amazonaws.services.dynamodbv2.model.*;
import com.google.common.base.*;
import com.google.common.collect.*;
import org.slf4j.*;

import java.util.*;

public class DynamoDbBroker {
    private static final Logger LOG = LoggerFactory.getLogger(DynamoDbMappingDao.class);
    private static final String VALUE_ATTRIBUTE_NAME = "longUrl";
    public static final String KEY_ATTRIBUTE_NAME = "shortUrl";

    private final DynamoDB dynamoDB;
    private final String tableName;

    public DynamoDbBroker(String dynamoDbUrl, String tableName) {
        AmazonDynamoDBClient client = selectDynamoDbClient(dynamoDbUrl);
        this.dynamoDB = new DynamoDB(client);
        this.tableName = tableName;
    }

    private AmazonDynamoDBClient selectDynamoDbClient(String dynamoDbUrl) {
        return System.getProperty("santel.url.local") == null?
                new AmazonDynamoDBClient().withRegion(Regions.US_WEST_2) :
                new AmazonDynamoDBClient().withEndpoint(dynamoDbUrl);
    }

    public boolean createTable() {
        try {
            ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput(10L, 10L);

            CreateTableRequest createTableRequest = getCreateTableRequest(provisionedThroughput);
            Table table = dynamoDB.createTable(createTableRequest);
            table.waitForActive();

            LOG.info("Successfully created table");
            return true;
        } catch (Exception e) {
            LOG.error("Failed to create table", e);
            return false;
        }
    }

    private CreateTableRequest getCreateTableRequest(ProvisionedThroughput provisionedThroughput) {
        GlobalSecondaryIndex globalSecondaryIndex = getGlobalSecondaryIndex(provisionedThroughput);

        return new CreateTableRequest(
                ImmutableList.of(new AttributeDefinition(KEY_ATTRIBUTE_NAME, ScalarAttributeType.S), new AttributeDefinition(VALUE_ATTRIBUTE_NAME, ScalarAttributeType.S)),
                tableName,
                ImmutableList.of(new KeySchemaElement(KEY_ATTRIBUTE_NAME, KeyType.HASH)),
                provisionedThroughput)
                .withGlobalSecondaryIndexes(ImmutableList.of(globalSecondaryIndex));
    }

    private GlobalSecondaryIndex getGlobalSecondaryIndex(ProvisionedThroughput provisionedThroughput) {
        return new GlobalSecondaryIndex()
                .withIndexName(VALUE_ATTRIBUTE_NAME)
                .withKeySchema(ImmutableList.of(new KeySchemaElement(VALUE_ATTRIBUTE_NAME, KeyType.HASH)))
                .withProvisionedThroughput(provisionedThroughput)
                .withProjection(new Projection()
                        .withProjectionType(ProjectionType.ALL));
    }

    public boolean deleteTable() {
        try {
            Table table = dynamoDB.getTable(tableName);
            table.delete();
            table.waitForDelete();

            LOG.info("Successfully deleted table");
            return true;
        } catch (Exception e) {
            LOG.error("Failed to delete table", e);
            return false;
        }
    }

    public boolean insertEntry(String key, String value) {
        try {
            Table table = dynamoDB.getTable(tableName);
            PutItemSpec putItemSpec = new PutItemSpec()
                    .withItem(new Item()
                            .withPrimaryKey(KEY_ATTRIBUTE_NAME, key)
                            .with(VALUE_ATTRIBUTE_NAME, value))
                    .withConditionExpression("attribute_not_exists(" + KEY_ATTRIBUTE_NAME + ") AND attribute_not_exists(" + VALUE_ATTRIBUTE_NAME + ")");
            table.putItem(putItemSpec);

            LOG.info("Successfully inserted entry {} -> {}", key, value);
            return true;
        } catch (Exception e) {
            LOG.error(String.format("Failed to insert entry %s -> %s", key, value), e);
            return false;
        }
    }

    public String queryValue(String key) {
        Table table = dynamoDB.getTable(tableName);
        ItemCollection<QueryOutcome> items = table.query(new KeyAttribute(KEY_ATTRIBUTE_NAME, key));
        IteratorSupport<Item, QueryOutcome> itemIterator = items.iterator();
        if (!itemIterator.hasNext()) {
            return null;
        }
        Item item = itemIterator.next();
        Preconditions.checkState(!itemIterator.hasNext());

        for (Map.Entry<String, Object> entry : item.attributes()) {
            if (entry.getKey().equals(VALUE_ATTRIBUTE_NAME)) {
                return String.class.cast(entry.getValue());
            }
            Preconditions.checkState(entry.getKey().equals(KEY_ATTRIBUTE_NAME));
        }
        throw new IllegalStateException("Shouldn't get here");
    }

    public String queryKey(String value) {
        Table table = dynamoDB.getTable(tableName);
        Index index = table.getIndex(VALUE_ATTRIBUTE_NAME);
        QuerySpec querySpec = new QuerySpec()
                .withKeyConditionExpression("#v = :value")
                .withNameMap(new NameMap().with("#v", VALUE_ATTRIBUTE_NAME))
                .withValueMap(new ValueMap().withString(":value", value));
        ItemCollection<QueryOutcome> items = index.query(querySpec);
        IteratorSupport<Item, QueryOutcome> itemIterator = items.iterator();
        if (!itemIterator.hasNext()) {
            return null;
        }
        Item item = itemIterator.next();
        //Preconditions.checkState(!itemIterator.hasNext()); // there might be more than one key for this indexed value

        for (Map.Entry<String, Object> entry : item.attributes()) {
            if (entry.getKey().equals(KEY_ATTRIBUTE_NAME)) {
                return String.class.cast(entry.getValue());
            }
            Preconditions.checkState(entry.getKey().equals(VALUE_ATTRIBUTE_NAME));
        }
        throw new IllegalStateException("Shouldn't get here");
    }
}
<file_sep>/src/test/java/org/santel/url/dao/DynamoDbBrokerTest.java
package org.santel.url.dao;

import org.testng.*;
import org.testng.annotations.*;

/** Tests will pass only if a DynamoDB instance is located in the given or default url */
@Test
public class DynamoDbBrokerTest {

    public static final String TEST_KEY = "a key";
    public static final String TEST_VALUE = "some value";
    private DynamoDbBroker dynamoDbBroker;

    @BeforeMethod
    void beforeDynamoDbBrokerMethod() {
        String dynamoDbUrl = System.getProperty("dynamodb.url", "http://localhost:8000"); //TODO add to Spring property system?
        dynamoDbBroker = new DynamoDbBroker(dynamoDbUrl, "TestTableDynamoDbBrokerTest");
        dynamoDbBroker.deleteTable(); // make sure test table starts clean
    }

    @AfterMethod
    void afterDynamoDbBrokerMethod() {
        dynamoDbBroker.deleteTable(); // clean up test table after test
    }

    @Test
    void canCreateAndDestroyTestTable() {
        AssertJUnit.assertTrue("Table creation should succeed", dynamoDbBroker.createTable());
        AssertJUnit.assertTrue("Table deletion should succeed", dynamoDbBroker.deleteTable());
    }

    @Test
    void canStoreAndReadUrlEntryByKey() {
        AssertJUnit.assertTrue("Table creation should succeed", dynamoDbBroker.createTable());
        AssertJUnit.assertTrue("First insertion should succeed", dynamoDbBroker.insertEntry(TEST_KEY, TEST_VALUE)); // first insertion succeeds
        AssertJUnit.assertEquals("Value for key should be what we inserted", TEST_VALUE, dynamoDbBroker.queryValue(TEST_KEY));
    }

    @Test
    void canStoreAndReadUrlEntryByValue() {
        AssertJUnit.assertTrue("Table creation should succeed", dynamoDbBroker.createTable());
        AssertJUnit.assertTrue("First insertion should succeed", dynamoDbBroker.insertEntry(TEST_KEY, TEST_VALUE)); // first insertion succeeds
        AssertJUnit.assertEquals("Key for value should be what we inserted", TEST_KEY, dynamoDbBroker.queryKey(TEST_VALUE));
    }

    @Test
    void doubleInsertionFails() {
        AssertJUnit.assertTrue("Table creation should succeed", dynamoDbBroker.createTable());
        AssertJUnit.assertTrue("First insertion should succeed", dynamoDbBroker.insertEntry(TEST_KEY, TEST_VALUE)); // first insertion succeeds
        AssertJUnit.assertFalse("Repeated insertion should fail", dynamoDbBroker.insertEntry(TEST_KEY, TEST_VALUE)); // second insertion fails
    }
}
<file_sep>/README.md
# url-shortener

## Goals
* A URL shortening RESTful service which produces short urls from “long” (any) urls and redirects requests for short urls to the original urls
* Basic availability via data persistence
* Scalability: single instance for this demo but ready to add load balancer and more instances 
* Validation of URL well-formedness
* Security: it should be hard for strangers to discover existing short urls whose expansions might be sensitive for the other users who created them

## Non-goals
For demo purposes, the focus is on the core service, not on a full-blown production- level web app. Therefore, the service does not offer:
* A UI
* Scalability beyond a single instance (but ready to add load balancer and multiple instances)
* High availability (e.g. redundancy) beyond data persistence
* Validation that a service exists and is up on the “long” URL
* Security: blacklist or slow down users who are trying to repeatedly guess short urls
<file_sep>/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.santel</groupId>
    <artifactId>url-shortener</artifactId>
    <packaging>war</packaging>
    <version>0.1.0</version>

    <name>url-shortener</name>
    <description>An example URL shortener using Spring Boot that can run on a single machine or AWS</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>org.santel.url.ShorteningService</start-class>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-bom</artifactId>
                <version>1.10.43</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Compile dependencies -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-dynamodb</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-ec2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-remote-shell</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Test dependencies -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.9.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19.1</version>
                <dependencies>
                    <!--<dependency>-->
                        <!--<groupId>org.apache.maven.surefire</groupId>-->
                        <!--<artifactId>surefire-junit47</artifactId>-->
                        <!--<version>2.19.1</version>-->
                    <!--</dependency>-->
                    <dependency>
                        <groupId>org.apache.maven.surefire</groupId>
                        <artifactId>surefire-testng</artifactId>
                        <version>2.19.1</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project><file_sep>/src/main/java/org/santel/url/model/MappingModel.java
package org.santel.url.model;

import com.google.common.base.*;
import org.santel.exception.*;
import org.santel.net.*;
import org.santel.url.dao.*;
import org.slf4j.*;
import org.springframework.stereotype.*;

import javax.inject.*;
import java.net.*;

@Component
public class MappingModel {
    private static final Logger LOG = LoggerFactory.getLogger(MappingModel.class);
    private static final String SHORT_URL_PROTOCOL = "http"; //TODO make more secure with https

    @Inject
    private MappingDao mappingDao;
    @Inject
    private AlphanumericEncoder alphanumericEncoder;

    public URL shortenUrl(URL longUrl) {
        Preconditions.checkNotNull(longUrl);

        // first, check store for existence of entry for long url
        URL shortUrl = mappingDao.getShortUrl(longUrl);
        if (shortUrl != null) {
            LOG.info("Long url {} has already been shortened to {}", longUrl, shortUrl);
            return shortUrl;
        }

        // create short url, trying multiple times in case of collisions
        int tries = 100;
        while (tries-- > 0) {
            // encode long url as an alphanumeric-base string
            String shortCode = alphanumericEncoder.encodeAlphanumeric(longUrl.toString());
            shortUrl = getShortUrlFromCode(shortCode);

            // check for entry in data store; store and return it if it doesn't exist (i.e. no collision)
            if (mappingDao.addUrlEntry(shortUrl, longUrl)) {
                LOG.info("Shortened {} to {}", longUrl, shortUrl);
                return shortUrl;
            }

            // long URL might have been added by another thread or instance in the meantime
            URL anotherShortUrl = mappingDao.getShortUrl(longUrl);
            if (anotherShortUrl != null) {
                LOG.info("Long url {} was shortened in the meantime to {}", longUrl, anotherShortUrl);
                return anotherShortUrl;
            }

            URL storedLongUrl = mappingDao.getLongUrl(shortUrl);
            Preconditions.checkState(storedLongUrl != null && !storedLongUrl.equals(longUrl)); // it must be a collision
        }

        String errorMessage = "Exhausted tries to generate a non-colliding short url";
        LOG.error(errorMessage);
        throw new RuntimeException(errorMessage);
    }

    public URL expandUrl(String shortCode) {
        URL shortUrl = getShortUrlFromCode(shortCode);
        URL longUrl = mappingDao.getLongUrl(shortUrl);
        if (longUrl == null) {
            Exceptions.logAndThrow(LOG, String.format("Short url %s not found in store", shortUrl));
        }
        LOG.info("Expanded {} to {}", shortUrl, longUrl);
        return longUrl;
    }

    private URL getShortUrlFromCode(String shortCode) {
        URL shortUrl;
        String hostName = Network.getLocalHostName();
        try {
            shortUrl = new URL(SHORT_URL_PROTOCOL, hostName, "/" + shortCode);
        } catch (MalformedURLException e) {
            LOG.error("Exception trying to form URL from protocol {}, local host name {}, and code {}: {}", SHORT_URL_PROTOCOL, hostName, shortCode, e);
            throw new RuntimeException("Can't form short URL from original url, local host name, and code", e);
        }
        return shortUrl;
    }
}
