Aeonics Quick Tutorials

Introduction

This documentation is intended for developers, devops, fullstack engineers or whomever has sufficient background knowledge to understand it. It shows how to use certain concepts of the Aeonics SDK and quickly start with a first implementaton.

The content of this guide is protected by intellectual property rights and is subject to the Aeonics Commercial License Agreement. The information contained in this documentation is provided as-is without any guarantee of correctness nor any other sort, it is provided in good faith and is regularly checked and updated. If some content is not clear, outdated or misleading, please let us know and we will try to fix it. Some fine internal details are voluntarily omitted from this guide, if you need more information about a specific aspect, please contact us.

The form and language used in this guide is non-formal and intended for trained professionals, therefore some technical details may be omitted and/or the examples provided may not work or compile as-is. This guide is thought to be used along the official javadoc that include the specifics of every class and method of the Aeonics SDK. If you see ways to improve this guide or if you believe some notions would benefit from more details, please let us know.

Conventions

In this document, you will encounter different notations. Words highlighted in blue or orange are special keywords that are important. Other words with a grey background means that it references a technical term or a code-specific keyword.

There are also some code samples or configuration bits that are displayed in separate blocks. The color and header of the block provides some context on how to read the content.

This block type contains Java code
This block type contains other unspecified information

This documentation is constantly evolving, so sections or paragraphs highlighted in yellow means that some additional content should be delivered soon...

Next steps

Additional documentation and next steps are :

  • Github: Some code samples and other material are publicly published on Github.
  • Javadoc: The official Aeonics SDK javadoc.
  • Technological Overview: The overview of core principles and terminology.
  • Developer Guide: The main documentation about the context and the global system principles.
  • Modules: Aeonics is a modular system and each module provides its own functionalities. This documentation lists the specific configuration parameters and detailed behavior of all officially supported module.
  • Aeonics Frontend Framework (AFF): Aeonics uses its own lightweight frontend single page application framework. You can reuse this framework to build your own applications.

Creating Rest Endpoints

The RestEndpoint class provides a simple and flexible way to define and handle RESTful endpoints in your application. With RestEndpoint, you can easily map URLs to methods and handle HTTP requests.

The inline-code approach of the RestEndpoint offers several advantages over systems that rely on runtime annotations or other complex frameworks:

Creating Endpoints

To create an endpoint, you can instantiate the RestEndpoint class and define the URL path and supported HTTP methods in the constructor. Here's an example:

public Endpoint a = new RestEndpoint("/path/a", "GET") {
    public Data handle(Data parameters) {
        return Data.map().put("hello", "world");
    }
};

In this example, the endpoint /path/a is mapped to the HTTP GET method, and the handle() method is overridden to return a Data object with a key-value pair. (See working with Data).

Handling Requests

The handle() method is where you define the logic for processing incoming requests and generating the response. It takes a Data object as a parameter, which contains the request parameters. You can access and manipulate these parameters as needed. Here's an example:

public Data handle(Data parameters) { 
    return Data.map().put("hello", parameters.get("name")); 
}

In this case, the handle() method retrieves the value of the "name" parameter from the request parameters and includes it in the response.

Adding Parameters

You can add parameters to your endpoint to define specific requirements or constraints. The add() method allows you to add parameters to the endpoint, which are automatically validated. It does not matter if the parameter commes from the query string or from the request body, it will be provided directly to the handle() method. Here's an example:

public Endpoint c = new RestEndpoint("/path/c", "GET") {
    public Data handle(Data parameters) {
        return Data.map().put("hello", parameters.get("name"));
    }
}.add(new Parameter("name").optional(false).max(50));

In this example, a required parameter named "name" is added to the endpoint. The parameter is set to be non-optional and has a maximum length of 50 characters.

Mapping Parameters in the URL

If you want to include parameters in the URL itself, you can use curly braces {} to specify the parameter name. The RestEndpoint class will automatically extract the values from the URL and make them available in the handle() method. Here's an example:

public Endpoint d = new RestEndpoint("/path/d/{name}", "GET") {
    public Data handle(Data parameters) {
        return Data.map().put("hello", parameters.get("name"));
    }
}.add(new Parameter("name").optional(false).max(50));

In this example, the parameter "name" is specified in the URL path. When a request is made to /path/d/somevalue, the value "somevalue" will be available as the "name" parameter in the handle() method.

Error Handling

If an error occurs while processing a request, you can throw a RestException with the desired HTTP response code. Uncaught exceptions will be returned as a code 500 error. Here's an example:

public Endpoint e = new RestEndpoint("/path/e", "GET") {
    public Data handle(Data parameters) throws Exception { 
        throw new RestException(400, "Some error happened"); 
    }
};

In this example, a RestException with a response code of 400 and an error message is thrown when handling the request.

Documentation

You can provide documentation for your endpoints and parameters to make it easier for other developers to understand and use them. The RestEndpoint class provides methods to set the summary and description for the endpoint. Here's an example:

public Endpoint d = new RestEndpoint("/path/d/{name}", "GET") {
    public Data handle(Data parameters) {
        return Data.map().put("hello", parameters.get("name"));
    }
}.add(new Parameter("name", "This is the description for the name parameter").optional(false).max(50))
 .summary("Simple hello world")
 .description("This endpoint will greet you in JSON format using the provided name.");

In this example, the summary() method sets a brief summary for the endpoint, while the description() method provides a detailed description. Additionally, the Parameter constructor accepts a description parameter to document the purpose or usage of the parameter.

Security and Filtering

If you need to validate the user and filter the request or response, you can use the TriggerRestEndpoint class instead of RestEndpoint. The TriggerRestEndpoint allows you to add security checks and apply filters to the request and response in the form of triggers. Here's an example:

public Endpoint f = new TriggerRestEndpoint("/path/f/{name}", "GET") {
    public Data handle(Data parameters, User user) { 
        return Data.map()
            .put("hello", parameters.get("name"))
            .put("you are", parameters.get("login"));
    }
}
.security((request, user) -> { 
    if (user == User.ANONYMOUS) 
        throw new SecurityException();
})
.before((request, user) -> { 
    request.put("login", user.name());
})
.after((request, response, user) -> { 
    response.put("modified", true);
})
.add(new Parameter("name").optional(false).max(50));

In this example, the security() method sets a security check that throws a SecurityException if the user is anonymous. The before() method is used to modify the request by adding the user's login name to the request parameters. The after() method allows you to modify the response by adding or altering its content. Each of these methods is optional.

In the before() method, you can manipulate the request parameters in place, or return an entire new set. Here is an example:

.before((request, user) -> { 
    return Data.map()
        .put("key", "value");
})

In the after() method, you can manipulate the endpoint response, or override it entirely with a new one. Here is an example:

.after((request, response, user) -> { 
    return Data.map()
        .put("new", "response");
})

Conclusion

You've now learned the basics of using the RestEndpoint class to define and handle RESTful endpoints in your application. With RestEndpoint, you can easily map URLs to methods, handle requests, add parameters, handle errors, and provide documentation. Additionally, the TriggerRestEndpoint class extends the functionality by allowing user validation and request/response filtering.

Feel free to explore the various methods and options available in the RestEndpoint class to customize and extend your RESTful API implementation according to your application's needs. Happy coding!

Working with Data

The Data class provides a flexible and convenient way to store and manipulate data in your Java applications. It allows you to store various types of data and easily coerce them into different types when needed. The Data class works in parallel with the Encodable, Decodable, and Updatable interfaces provided by the aeonics.data package.

When data is subject to frequent changes, using the Data class can offer advantages over an Object-Relational Mapping (ORM) approach.

Creating a Data object

There are different ways to create a Data object from scratch: you can create a Data object using the Data.map() method, which creates a new empty map-like data structure, using Data.list() which creates a new array-like structure, or using Data.wrap(value) which will encapsulate an axisting value in a Data object.

Data data_map = Data.map();
Data data_list = Data.list();
Data data_value = Data.wrap("some value");

Adding Values

Once you have a Data object, you can add values to it using the put() method if it is a map, or the add() method if it is a list. You can store values of any type, such as strings, booleans, numbers, or even nested Data objects:

data.put("foo", "bar");
data.put("test", true);
data.put("answer", 42);
data.put("array", Data.list().add(1).add("a").add(null));

Accessing and Manipulating Values

You can access the stored values in the Data object using various methods. Here are some examples:

String fooValue = data.asString("foo");
int answerValue = data.asInt("answer");
boolean testValue = data.asBool("test");

If you do not know the exact native Java type, you can perform type coercion to convert values from one type to another automatically:

int intValue = Data.of("42").asInt(); // Coerces the string "42" to an integer (42)
boolean boolValue = Data.of("true").asBool(); // Coerces the string "true" to a boolean (true)
int boolToInt = Data.of(false).asInt(); // Coerces the boolean false to an integer (0)
String boolToString = Data.of(true).asString(); // Coerces the boolean true to a string ("true")

Removing Values

If you need to remove a value from the Data object, you can use the remove() method. If you specify a String key, it will remove it from the map. If you provide a numerical index, it will remove it from the list:

data.remove("answer");
data.get("array").remove(2);

Merging Data

To combine two Data objects, you can use the merge() method. This method merges the contents of the specified Data object into the current Data object:

Data data2 = Data.map().put("hero", "batman");
data.merge(data2); // Merges the contents of data2 into the current data object

Checking Values and Types

The Data class provides several methods to check the existence and type of values:

boolean containsFoo = data.containsKey("foo"); // Checks if the key "foo" exists
boolean isFooNull = data.isNull("foo"); // Checks if the value associated with "foo" is null
boolean isFooString = data.isString("foo"); // Checks if the value associated with "foo" is a string
boolean isFooBool = data.isBool("foo"); // Checks if the value associated with "foo" is a boolean
boolean isFooNumber = data.isNumber("foo"); // Checks if the value associated with "foo" is a number
boolean isFooAnswer = data.is("foo", Answer.class); // Checks if the value associated with "foo" is of type Answer
boolean isFooEmpty = data.isEmpty("foo"); // Checks if the value associated with "foo" is empty or null

Encoding and Decoding to Custom Classes

You can also encode and decode data to custom classes by implementing the Decodable interface. Here's an example:

public static class Answer implements Decodable {
    int value;
    public void decode(Data data) {
        value = data.asInt();
    }
}

You can decode a Data object into an instance of the Answer class using the as() method:

Answer answer = data.get("answer").as(Answer.class);

JSON Representation

The Data class provides a convenient way to represent data in JSON format. You can obtain the JSON representation of a Data object using the toString() method:

String json = data.toString(); // Returns the JSON representation of the data object

You can also decode a JSON string into a Data object using the Json.decode() method:

Data decodedData = Json.decode(json); // Decodes the JSON string into a Data object

Conclusion

The Data class offers a convenient and flexible way to store and manipulate data in your Java applications. Its ease of use and versatility make it a powerful tool for handling various data types and performing type coercion. Whether you need to store simple values or complex nested structures, the Data class provides a convenient API to make your data handling tasks easier and more efficient.

Item, Registry, Factory

tutorial about the registry and factory