{#========================================== Docs : "Miscellaneous" ==========================================#}

Miscellaneous

{#========================================== Default Configurations ==========================================#}

Default Configurations

To know what are the default Spincast configurations, have a look at the Spincast Config plugin page, which is the default implementation of the "ISpincastConfig" interface. But here are some important ones:

{#========================================== Templating engine ==========================================#}

Templating engine (view engine)

The templating engine (also called view engine, or template engine), is the component you use to generate dynamic HTML pages. It can be used for other purposes, for example to generate the body of an email when some placeholders have to be replaced in the base template, but its most frequent use is to generate HTML pages.

You can inject the ITemplatingEngine component anywhere you need it, but to generate HTML pages the preferred way is to use the methods provided by the response() add-on, on the request context objects:

public class AppController {

    public void myRouteHandler(IAppRequestContext context) {

        IUser user = getUser();

        Map<String, Object> params = new HashMap<String, Object>();
        params.put("user", user);

        // A template which is on the classpath:
        context.response().sendHtmlTemplate("/templates/user.html",
                                            params);

        // Or, a template which is on the file system:
        context.response().sendHtmlTemplate("/usr/www/myProject/templates/user.html",
                                            false,
                                            SpincastStatics.params("user", user));
    }
}

Explanation :

Here's an example template file which would be on the classpath (for example, at src/resources/templates/user.html, in the project). Note that the syntax used inside your templates depends on the implementation of the templating engine you use! In this example, we use the default implementation, Pebble:

{% verbatim %}

<!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Test</title>
    </head>
    <body>
        <h1>Hello {{user.name}}!</h1> 
    </body>
</html>
{% endverbatim %}

Note that you can also access the templating engine via its own "templating()" add-on, on the request context objects. This can be useful when you need to generate some dynamic content without sending it as the response for the current request:

public class AppController {

    public void myRouteHandler(IAppRequestContext context) {

        IUser user = getUser();
        
        String emailBody = context.templating().fromTemplate("/templates/email.html", 
                                                             SpincastStatics.params("user", user));
        // Do something with the 'emailBody'...
    }
}

Default templating variables

Spincast automatically adds some variables that can be used by the templating engine, in the scope of a request. Those variables are added by the "before" filter addDefaultGlobalTemplateVariables(...)

{#========================================== SSL ==========================================#}

Using a SSL certificate (HTTPS)

It is recommended that you serve your application over HTTPS and not HTTP, which is not secure. To achieve that, you need to install a SSL certificate.

If you download the Quick Start application, you will find two files explaining the required procedure:

{#========================================== IJsonObject ==========================================#}

IJsonObject / IJsonArray

The IJsonObject and IJsonArray are components provided by Spincast to mimic real Json objects and arrays.

Spincast uses those objects in many places. For example, to get the content of a request for which a Json body has been sent via Ajax :

public void myHandler(IAppRequestContext context) {
    IJsonObject json = context.request().getJsonBodyAsJsonObject();
    // ...
}}

Or to get submitted Form Datas :

public void myHandler(IAppRequestContext context) {
    IJsonObject formDatas = context.request().getFormDatas();
    // ...
}

IJsonObject and IJsonArray expose put(...) / add(...) methods for all the types they manage natively, and a putConvert(...) / addConvert(...) method to add any other type.

When adding an object of a type that is not managed natively, this object is converted to an IJsonObject (or an IJsonArray, if the source is an array or a Collection) by serializing and deserializing it using IJsonManager#toJsonString(...) and IJsonManager#create(...). By default, this is done using Jackson.

The IJsonObject and IJsonArray objects also have getters. For example, on IJsonObject :

On IJsonArray, getters require an index instead of a key : Every getter has an overloaded version that you can use to provide a default value in case the requested element if not found. By default null is returned if the element is not found.

Creating an IJsonObject or an IJsonArray

You can create an IJsonObject or an IJsonArray using the IJsonManager component. This component can be injected by Guice, or it can be accessed through the json() add-on, when you are inside a route handler :

public void myHandler(IAppRequestContext context) {

    // IJsonArray creation
    IJsonArray array = context.json().createArray();
    array.add(111);
    array.add(222);

    // IJsonObject creation
    IJsonObject obj = context.json().create();
    obj.put("name", "Stromgol");
    obj.put("lastName", "Laroche");
    
    // Or, using the IJsonManager directly :
    IJsonObject obj2 = getJsonManager().create();

    context.response().sendJsonObj(obj);
}

Cloning and Immutability

By default, any element added to an IJsonObject or an IJsonArray is added as is, without being cloned. This means that if you add an IJsonObject or an IJsonArray element, any external modification will affect the added element, and vice-versa, since they both refere to the same instance. This allows you to do something like :

IJsonArray colors = getJsonManager().createArray();
IJsonObject obj = getJsonManager().create();

// Add the array to the obj :
obj.put("colors", colors);

// Later... add elements to the array :
colors.add("red");
colors.add("blue");

// This returns "red" : the array inside the IJsonObject
// is the same instance than the external one.
String firstColor = obj.getJsonArray("colors").getString(0);

But sometimes this behavior is not wanted. You may need the external object and the added object to be two distinct instances, so modifications to one don't affect the other! In those cases, you can call the "clone()" method on the original IJsonObject or IJsonArray object. You can also use "true" as the "clone" parameter when using put(...)/add(...) methods, to achieve the same result :

IJsonArray colors = getJsonManager().createArray();
IJsonObject obj = getJsonManager().create();

// Add a *clone* of the array to the object :
obj.put("colors", colors, true);
// Or, same result :
obj.put("colors", colors.clone());

// Add elements to the array :
colors.add("red");
colors.add("blue");

// This will now return null since a *new* instance of the 
// array has been added to the IJsonObject!
String firstColor = obj.getJsonArray("colors").getString(0);

The generated clones is a deep copy of the original object, which means the root object and all the children are cloned.

We also decided to make IJsonObject and IJsonArray objects mutable by default. This is a conscious decision to make those objects easier to work with : you can add and remove elements from them at any time.

If you need more safety, if you work with a complex multi-threaded application, or if you simply prefer to have an immutable version of those objects, you can get one using their .clone(false) method, by making sure the "mutable" parameter is set to false :

IJsonObject obj = getJsonManager().create();
obj.put("name", "Stromgol");

IJsonObject immutableClone = obj.clone(false);

// This will now throw an exception! :
immutableClone.put("nope", "doesn't work");

When you create such immutable clones, the root element and all the children are cloned as immutable. In fact, IJsonObject and IJsonArray objects are always fully mutable or fully immutable... Note that if you try to add an immutable object to a mutable one, a mutable clone will be created from the immutable object before being added.

At runtime, you can validate if your object is mutable or not using : if(obj instanceof Immutable).

IJsonObject methods

Have a look at the IJsonObject Javadoc for a complete list of available methods. Here are some interesting ones, other than put(...), getXXX(...) and clone(...), that we already introduced :

IJsonArray methods

Have a look at the IJsonArray Javadoc for a complete list of available methods. Here are some interesting ones, other than add(...), getXXX(...) and clone(...), that we already introduced :

{#========================================== Spincast Utilities ==========================================#}

Spincast Utilities

Spincast provides some generic utilities, accessible via the ISpincastUtils interface:

{#========================================== @MainArgs ==========================================#}

@MainArgs

Both SpincastCoreGuiceModule and SpincastDefaultGuiceModule Guice modules have a constructor which accepts String[] mainArgs. You can pass to it the arguments received in your main(...) method. For example:

public static void main(String[] args) {

    Injector guice = Guice.createInjector(new SpincastDefaultGuiceModule(args));

    App app = guice.getInstance(App.class);
    app.start();
}

By doing so, those arguments will be bound, using a @MainArgs annotation. You can then inject them anywhere you need:

public class AppConfig extends SpincastConfig implements IAppConfig {

    private final String[] mainArgs;

    @Inject
    public AppConfig(@MainArgs String[] mainArgs) {
        this.mainArgs = mainArgs;
    }

    protected String[] getMainArgs() {
        return this.mainArgs;
    }

    @Override
    public int getHttpServerPort() {

        int port = super.getHttpServerPort();
        if(getMainArgs().length > 0) {
            port = Integer.parseInt(getMainArgs()[0]);
        }
        return port;
    }
}

{#========================================== Using an init() method ==========================================#}

Using an init() method

This is more about standard Guice development than about Spincast, but we feel it's a useful thing to know.

Guice doesn't provide support for a @PostConstruct annotation out of the box. And since it is often seen as a bad practice to do too much work directly in a constructor, what we want is an init() method to be called once the object it fully constructed, and do the initialization work there.

The trick is that Guice calls any @Inject annotated methods once the object is created, so let's use this to our advantage:

public class UserService implements IUserService {

    private final ISpincastConfig spincastConfig;

    @Inject
    public UserService(ISpincastConfig spincastConfig) {
        this.spincastConfig = spincastConfig;
    }

    @Inject
    protected void init() {
        doSomeValidation();
        doSomeInitialization();
    }

    //...
}

Explanation :

What we recommend is constructor injection + one (and only one) @Inject annotated method. The problem with multiple @Inject annotated methods (other than constructors) is that it's hard to know in which order they will be called.

Finally, if the init() method must be called as soon as the application starts, make sure you bind the object using asEagerSingleton()!