Skip to content

Added Generate Short URL example in Java #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 283 additions & 0 deletions java/short_url_generator/Index.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.*;
import java.util.stream.*;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import com.google.gson.Gson;
import org.apache.commons.validator.routines.UrlValidator;

// List of supported providers
private enum Provider {

BITLY("bitly"),
TINY_URL("tinyurl");

private final String name;

Provider(String name) {
this.name = name;
}

static boolean validateProvider(String name) {
for(Provider providerName: Provider.values()) {
if(providerName.name.equals(name)) {
return true;
}
}

return false;
}

String getName() {
return name;
}
}

final Gson gson = new Gson();

final Map<String, String> endpointsMap = Map.of(
"bitly", "https://api-ssl.bitly.com/v4/shorten",
"tinyurl", "https://api.tinyurl.com/create"
);

public RuntimeResponse main(RuntimeRequest req, RuntimeResponse res) throws Exception {

// Validate that values present in the request are not empty (payload, variables)
RuntimeResponse errorResponse = checkEmptyPayloadAndVariables(req, res);
if(errorResponse != null) {
return errorResponse;
}

// Validate the requested payload (provider and URL)
String payloadString = req.getPayload();
Map<String, Object> payload = gson.fromJson(payloadString, Map.class);

errorResponse = validatePayload(payload, res);
if(errorResponse != null) {
return errorResponse;
}

// Generate short url
String provider = payload.get("provider").toString();
String url = payload.get("url").toString();

// Validate that the API key is not empty
String apiKeyVariable = Provider.BITLY.getName().equals(provider) ? "BITLY_API_KEY" : "TINYURL_API_KEY";

errorResponse = checkEmptyAPIKey(req, res, apiKeyVariable);
if(errorResponse != null) {
return errorResponse;
}

String apiKey = req.getVariables().get(apiKeyVariable);

String shortUrl = "";

Map<String, Object> responseData = new HashMap<>();

try {
shortUrl = generateShortUrl(apiKey, provider, url);
} catch(Exception e) {
responseData.put("success", false);
responseData.put("message", "Something went wrong while generating the shortUrl, please check with the developers. Error: " + e.getMessage());
return res.json(responseData);
}

// Validate that the generated short URL is not empty
errorResponse = checkEmptyShortUrl(shortUrl, res);
if(errorResponse != null) {
return errorResponse;
}

// Send response
responseData.put("success", true);
responseData.put("url", shortUrl);

return res.json(responseData);
}

/**
* This method validates that the generated short URL is not empty
*
* @param shortUrl is the URL to be validated
* @return null if shortURL is non-empty, otherwise an error response
*/
private RuntimeResponse checkEmptyShortUrl(String shortUrl, RuntimeResponse res) {
Map<String, Object> responseData = new HashMap<>();

if(shortUrl == null || shortUrl.trim().isEmpty()) {
responseData.put("success", false);
responseData.put("message", "Blank or null shortUrl value is returned, please try again or check with the developers");
return res.json(responseData);
}

return null;
}

/**
* This method validates that non-empty provider and URL are present in the payload
* It also validates that the requested provider is one of the supported providers
*
* @param payload is the object that contains the provider and the URL
* @return null if payload is valid, otherwise an error response
*/
private RuntimeResponse validatePayload(Map<String, Object> payload, RuntimeResponse res) {
Map<String, Object> responseData = new HashMap<>();

// Validate that payload has both provider and url
if(!payload.containsKey("provider") || !payload.containsKey("url")) {
responseData.put("success", false);
responseData.put("message", "Payload must contain both provider and url data");
return res.json(responseData);
}

String provider = payload.get("provider").toString();
String url = payload.get("url").toString();

// Validate the provider
if(!Provider.validateProvider(provider)) {
responseData.put("success", false);
String providerNames = Stream.of(Provider.values())
.map(Provider::getName)
.collect(Collectors.joining(", "));
responseData.put("message", "Provider " + provider + " is not supported currently. " +
"Only " + providerNames + " are supported");
return res.json(responseData);
}

// Validate the URL
UrlValidator urlValidator = new UrlValidator();
if (!urlValidator.isValid(url)) {
responseData.put("success", false);
responseData.put("message", "Provided URL: " + url + " is not valid, please provide a valid, correctly formed URL");
return res.json(responseData);
}

return null;
}

/**
* This function will validate that the payload and variables are non-empty in the request
*
* @param req is the received POST request
* @return null is nothing is empty, otherwise an error response
*/
private RuntimeResponse checkEmptyPayloadAndVariables(RuntimeRequest req, RuntimeResponse res) {
Map<String, Object> responseData = new HashMap<>();

if(req.getPayload() == null || req.getPayload().trim().isEmpty() || req.getPayload().trim().equals("{}")) {
responseData.put("success", false);
responseData.put("message", "Payload is empty, expected a payload with provider and URL");
return res.json(responseData);
}

if(req.getVariables() == null) {
responseData.put("success", false);
responseData.put("message", "Empty function variables found. You need to pass an API key for the provider");
return res.json(responseData);
}
return null;
}

/**
* This method validates that a non-empty API key is present in variables
*
* @param req is the received POST request
* @return null if non-empty API key is present, otherwise an error response
*/
private RuntimeResponse checkEmptyAPIKey(RuntimeRequest req, RuntimeResponse res, String apiKeyVariable) {
Map<String, String> variables = req.getVariables();

if(!variables.containsKey(apiKeyVariable)
|| variables.get(apiKeyVariable) == null
|| variables.get(apiKeyVariable).trim().isEmpty()) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("success", false);
responseData.put("message", "Please pass a non-empty API Key " + apiKeyVariable + " for the provider");
return res.json(responseData);
}

return null;
}

/**
* This method will generate a short URL for the given long URL and provider using the provider's API key
* It will generate the request body and parse the response according to the provider
*
* @param apiKey is the access token used by the provider to generate a short URL
* @param provider is the service that will generate the short URL. E.g. tinyurl, bitly
* @param url is the URL to be shortened
* @return the shortened URL
* @throws Exception in case of malformed URL, I/O exception, etc.
*/
private String generateShortUrl(String apiKey, String provider, String url) throws Exception {
if(apiKey == null || apiKey.trim().isEmpty()) {
return null;
}

String requestBody = "";

if(Provider.BITLY.getName().equals(provider)) {
requestBody = "{\"long_url\": \"" + url + "\"}";
} else if(Provider.TINY_URL.getName().equals(provider)) {
requestBody = "{\"url\": \"" + url + "\"}";
}

if(requestBody.isEmpty()) {
return null;
}

String response = getShortUrlFromProvider(endpointsMap.get(provider), requestBody, apiKey);
Map<String, Object> parsedResponse = gson.fromJson(response, Map.class);

if(Provider.BITLY.getName().equals(provider)) {
return parsedResponse.get("link").toString();
} else if(Provider.TINY_URL.getName().equals(provider)) {
Map<String, Object> responseData = (Map) parsedResponse.get("data");
return responseData.get("tiny_url").toString();
}

return null;
}

/**
* This method will send a POST request to the specified endpoint and return the provider's response
*
* @param endpointUrl is the provider's POST endpoint to which the URL generation request is to be sent
* @param requestBody is the Request Body for the POST request containing the URL to be shortened
* @param apiKey is the access token used by the provider to generate a short URL
* @return the provider's response to the POST request
* @throws Exception in case of malformed URL, I/O exception, etc.
*/
private String getShortUrlFromProvider(String endpointUrl, String requestBody, String apiKey) throws Exception {
URL url = new URL(endpointUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();

con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", "Bearer " + apiKey);
con.setRequestProperty("Accept", "application/json");
con.setDoOutput(true);

OutputStream os = con.getOutputStream();
byte[] input = requestBody.getBytes("utf-8");
os.write(input, 0, input.length);

StringBuilder response = new StringBuilder();

BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}

br.close();
con.disconnect();

return response.toString();
}
91 changes: 91 additions & 0 deletions java/short_url_generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 💻 Get Short URL

A Java Cloud Function for generating a Short URL using [tinyurl](https://tinyurl.com/app) and [bitly](https://bitly.com/)

Supported providers: tinyurl, bitly

_Bitly Example input:_



```json
{
"provider": "bitly",
"url": "https://google.com"
}
```

_Bitly Example output:_


```json
{
"success": true,
"url": "https://bit.ly/3CywbjA"
}
```

_Tinyurl Example input:_



```json
{
"provider": "tinyurl",
"url": "https://google.com"
}
```

_Tinyurl Example output:_


```json
{
"success": true,
"url": "https://tinyurl.com/dck67zxk"
}
```

_Error Example output:_

```json
{
"success": false,
"message":"Provided URL: https://xyz is not valid, please provide a valid, correctly formed URL"
}
```


## 📝 Environment Variables

List of environment variables used by this cloud function. These must be passed within 'variables' section of the request:

**BITLY_API_KEY** - Pass this API key if the requested provider is bitly. This API key would be the access token used by bitly for generating a short URL.

**TINYURL_API_KEY** - Pass this API key if the requested provider is tinyurl. This API key would be the access token used by tinyurl for generating a short URL.

## 🚀 Deployment

1. Clone this repository, and enter this function folder:

```
$ git clone https://github.com/open-runtimes/examples.git && cd examples
$ cd java/short_url_generator
```

2. Enter this function folder and build the code:
```
docker run -e INTERNAL_RUNTIME_ENTRYPOINT=Index.java --rm --interactive --tty --volume $PWD:/usr/code openruntimes/java:v2-11.0 sh /usr/local/src/build.sh
```
As a result, a `code.tar.gz` file will be generated.

3. Start the Open Runtime:
```
docker run -p 3000:3000 -e INTERNAL_RUNTIME_KEY=secret-key --rm --interactive --tty --volume $PWD/code.tar.gz:/tmp/code.tar.gz:ro openruntimes/java:v2-11.0 sh /usr/local/src/start.sh
```

Your function is now listening on port `3000`, and you can execute it by sending `POST` request with appropriate authorization headers. To learn more about runtime, you can visit Java runtime [README](https://github.com/open-runtimes/open-runtimes/tree/main/runtimes/java-11.0).

## 📝 Notes
- This function is designed for use with Appwrite Cloud Functions. You can learn more about it in [Appwrite docs](https://appwrite.io/docs/functions).
- This example is compatible with Java 11.0. Other versions may work but are not guarenteed to work as they haven't been tested.
4 changes: 4 additions & 0 deletions java/short_url_generator/deps.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies {
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'commons-validator:commons-validator:1.7'
}