Skip to content
This repository has been archived by the owner on Dec 29, 2024. It is now read-only.

This is a service that creates customized email addresses by processing user-defined inputs using a custom expression language.

License

Notifications You must be signed in to change notification settings

stdNullPtr/Java-Custom-Language-Interpreter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java Custom Language Interpreter (Dynamic Email Generator) Docker Image CI

  1. Introduction
  2. Technologies Used
  3. Setup Instructions
  4. Custom Expression Language
  5. API Endpoints
  6. Extending the Custom Expression Language

Introduction

The Dynamic Email Generator is a service powered by the Spring Boot framework, offering a RESTful API to create personalized email addresses. Through a custom expression language, users can define rules to generate email addresses based on specific criteria and custom methods.

The service has a reverse Nginx proxy at the front, with the whole setup configured inside a docker-compose file for easy setup.

Technologies Used

  • Java 21
  • Spring Boot 3
  • Nginx
  • Docker
  • Postman

Setup Instructions

Note: The following instructions were written and tested on a Windows 10 system

Certificates

Nginx

To create the self-signed certificates needed for the Nginx reverse proxy configuration, you must have OpenSSL installed on your system. You most likely already have it in you git installation folder ("C:\Program Files\Git\usr\bin\openssl.exe") just add it to PATH.

After installing OpenSSL (and placing it on your PATH) you have 2 options:

  1. Navigate to \nginx
  2. Open a CMD window in that location
  3. Run the helper script create_certs.bat
    create_certs script example

Or manually:

  1. Open a CMD window
  2. Navigate to the \nginx folder in project root directory
  3. Create a \certs directory if not present: run mkdir certs
  4. To generate a self-signed certificate run openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout certs/nginx.key -out certs/nginx.crt -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.localhost.com"

In both cases you will end up with a .crt and .key file in the \nginx\certs directory.

Note that they are referenced in docker-compose.yml and nginx.conf and must be updated in both places if a name/location change occurs.

NOTE: The generated self-signed certificate is for development/testing purposes.

Spring Service

To create self-signed certificates and a keystore for the service, you can use keytool command that comes with the JDK. To create a default certificate that is used in the default environment setup described in this documentation - you can use the provided create_default_keystore.bat script inside the /tls folder. YOU MUST HAVE keytool command present in PATH or edit the script to launch keytool from its' location C:\Users\<user>\.jdks\corretto-21.0.5\bin\keytool ...

To use the helper script:

  1. Navigate to /tls directory
  2. Open a CMD window at that location
  3. Run the helper script create_default_keystore.bat
    create_keystore script example
  4. You should end up with a new directory inside /tls and a keystore file inside
    keystore example

NOTE: The generated self-signed certificate is for development/testing purposes.

Nginx Reverse Proxy Configuration

All the nginx-related configurations can be found under \nginx. This includes certificates, custom error pages, helper scripts and most importantly - nginx.conf.

The important aspects of the proxy configuration are:

  • SSL certificate locations (referenced in docker-compose)
    ssl_certificate /etc/nginx/certs/nginx.crt;
    ssl_certificate_key /etc/nginx/certs/nginx.key;
  • Error page config (referenced in docker-compose)
     # Capture unexpected errors gracefully
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
        internal;
    }
  • location configs - the configuration is strict, only exposing the endpoints that are relevant for operation, preventing external users from malicious attempts
    • location /app - api prefix
    • location /swagger-ui - needed for swagger ui
    • location /v3 - needed for swagger ui

Note: Nginx is exposed on port 9443, but listening on 443 internally, and if this has to be changed, docker-compose has to be updated to reflect the change, also nginx.conf location objects must be updated with the new port, namely proxy_set_header X-Forwarded-Port 9443;

Running the docker-compose Bundle Locally

To run the configuration described inside the docker.compose.yml, you must have Docker installed on your system.

Note: there is a .dev-env environment file that holds a default keystore path and password needed for the Spring service SSL config. It must be passed to the docker compose command as described below.

Build the project before creating a Docker image:

  1. Run a mvn clean verify on the project
  2. After all tests have passed, you should see a success build

After installing Docker:

  1. Open a CMD window
  2. Navigate to the project root directory
  3. Run the compose file, recreating any containers and forcing a rebuild
    • docker-compose --env-file ./.dev-env up --force-recreate --no-deps --build
  4. Clean up dangling containers (if any) produced by the previous command
    • docker image prune -f

This will build the new service Docker image described in the Dockerfile, run it, and pull and run the Nginx image serving as a reverse proxy.

Verify that you can hit the API swagger page at https://localhost:9443/swagger-ui/index.html

Custom Expression Language

Assume inputs:

str1=Ivan
str2=Petar
str3=Falcon
Operation/Expression Description
first(str1,N) evaluates to the first N chars of the input parameter
last(str1,N) evaluates to the last N chars of the input parameter
substr(str1, N start, N end) evaluates to the substring between start(inclusive) and end(exclusive) indexes
lit(str1) literal expression, simply evaluates to the value of str1 key
raw(.com) evaluates to a raw string: ".com" in the example
lit(str1) ; raw(.com) concatenation is done using a ";" semicolon
longer(lit(str1),lit(str2),lit(str3)) if first expression parameter is longer than second expression parameter -> execute third expression, if not - empty string
eq(lit(str1),lit(str2),lit(str3)) if first expression parameter is equal to second expression parameter -> execute third expression , if not - empty string

API Endpoints

Postman Collection

A postman collection that covers basic API usage can be found in /postman folder in the project root directory.

To import the collection:

  1. Open Postman
  2. Click on "import" on the left
    postman_import
  3. Drag and drop the collection from /postman directory
  4. Explore the stored requests
    postman_requests

Generate Emails

Endpoint: GET /app/v1/email/generate

This endpoint dynamically generates email addresses with the use of a Custom Expression Language and a set of input parameters. It's designed to process complex rules defined in the 'expression' parameter and apply them to the inputs provided.

Request Query Parameters:

  • expression (required): A string parameter defining the rules for generating emails. It must be a single, non-empty string. The rules are defined here.
  • strN: Additional parameters with keys starting with str followed by any number ( e.g., str1=test, str2=string). These parameters are used as inputs for the expression defined.

Responses:

Usage Notes:

  • The endpoint strictly requires at least two query parameters: one expression and at least one input parameter prefixed with str. This ensures that there is at least one rule and one input to apply the rule to.
  • The endpoint is designed to be robust against invalid input formats and will provide descriptive error messages to assist in correcting request formats.

Sample Requests:

curl -X 'GET' \
  'http://localhost:8080/app/v1/email/generate?str1=Ivan&str1=Petar&str1=Rado&str2=gmail&str2=yahoo&expression=first(str1, 3);raw(@);last(str2,4);raw(.com)' \
  -H 'accept: application/json'
curl -X 'GET' \
  'http://localhost:8080/app/v1/email/generate?expression=longer(lit(str1),lit(str2),lit(str3));first(str1, 3);raw(@);last(str2,4);raw(.com);eq(lit(str1),lit(str2),raw(.bg))&str1=Ivan&str1=Petar&str1=Radooo&str2=gmail&str2=yahoo&str3=test&str3=domain' \
  -H 'accept: application/json'

Extending the Custom Expression Language

The Custom Expression Language can be further extended with new expression methods very easily. The reason for that is the use of the Interpreter Design Pattern at the core of the project.

The simplest form of a new terminal expression:

@RequiredArgsConstructor
class SomeNewExpression implements Expression {
    private final String inputKey;

    @Override
    public String interpret(final Context ctx) {
        final String input = ctx.getValue(inputKey);
        return doSomeNewStringManipulation(input);
    }
}

The simplest form of a new non-terminal expression

@RequiredArgsConstructor
class SomeComplexConditionalExpression implements Expression {
    private final Expression left;
    private final Expression right;
    private final Expression actionWhenTrue;
    private final Expression actionWhenFalse;

    @Override
    public String interpret(final Context ctx) {
        if (left.interpret(ctx).equals(right.interpret(ctx))) {
            return actionWhenTrue.interpret(ctx);
        }
        return actionWhenFalse;
    }
}

As you can see new expressions can be defined incredibly easy, you just have to stick to these steps:

  1. Define a new expression method keyword, for example len()
  2. Create a new enum entry for the new expression keyword in Operations enum
    • LEN("len")
  3. Create a new Expression class in the interpreter package
  4. Add a new case in the operation switch inside Interpreter class instantiating a new Expression from the newly created Expression class
  5. Add proper tests, update documentation, update postman collection

That's it! In these short few steps you can add new expressions to the interpreter.

About

This is a service that creates customized email addresses by processing user-defined inputs using a custom expression language.

Topics

Resources

License

Stars

Watchers

Forks

Languages