templates.docs.miscellaneous.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{#==========================================
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:
-
getServerHost() : The host/IP the HTTP server
will listen on. The default is 0.0.0.0, which
means the server will listen on any IP.
-
getHttpServerPort() : The port the server
will listen to for HTTP (unsecure) requests.
If <= 0, the server won't listen on HTTP requests.
-
getHttpsServerPort() : The port the server
will listen to for HTTPS (secure) requests.
If <= 0, the server won't listen on HTTPS requests.
If you use HTTPS, you also have to provide some extra
configurations related to the SSL
certificate to use.
-
isDebugEnabled() : If true,
a development environment is taken for granted, and
internal error messages may be displayed publicly, no cache will be
used for the templates, etc. The default is true, so make
sure you change this to false before deploying to
production!
{#==========================================
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 :
-
11 : We use the
response()
add-on to access the sendHtmlTemplate(...) method. This method
takes two parameters: the path to the HTML template to use (listed below)
and a set of variables to use to generate the final output. By default, the
templating engine will try to find the template file on the classpath.
-
12 : We pass a
user
object to the templating engine so it can use it to
generate the final HTML. Here, we do so using a classic
Map<String, Object> structure (7-8).
-
15-16 : By passing
false as
a second parameter to the sendHtmlTemplate(...) method, we can also tell
the templating engine to search for the template
on the file system, and not on the classpath.
-
17 :
SpincastStatics.params(...)
is a utility method provided by Spincast to easily create
Map<String, Object> parameters, inline. It is equivalent to
creating a new HashMap<String, Object>() as in the first example (7-8).
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(...)
-
langAbrv : The abreviation of the current Locale to use. For example :
"en".
-
cacheBuster : The current cache buster code.
-
routeId : The id of the current route (its main handler).
-
fullUrl : The full URL of the current request.
-
isHttps : Is the current URL secure (HTTPS)?
-
pathParams : The parameters parsed from the path of the request. To be used
like {% verbatim %}
{{pathParams.myParam}}{% endverbatim %}.
-
qsParams : The parameters parsed from the queryString of the request. Note that
a queryString parameter can contain more than one values. To access the first value, use something like :
{% verbatim %}
{{qsParams.myParam[0]}}{% endverbatim %}.
-
cookies : The current cookies objects. To be used
like {% verbatim %}
{{cookies.myCookie.value}}{% endverbatim %}.
-
requestScopedVars : The request scoped variables added by the various handlers. To be used
like {% verbatim %}
{{requestScopedVars.myVar}}{% endverbatim %}.
{#==========================================
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:
-
/varia/ssl_certificate_howto/self-signed.txt
Shows how to use a self-signed certificate, for development
purpose.
-
/varia/ssl_certificate_howto/lets-encrypt.txt
Shows how to use a Let's Encrypt
certificate. Let's Encrypt is a provider of free, but totally valid,
SSL certificates. Instructions in this file will probably work for certificates
obtained from other providers, but we haven't tested it yet.
{#==========================================
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 :
-
String getString(String key)
-
String getString(String key, String defaultValue)
-
Integer getInteger(String key)
-
IJsonObject getJsonObject(String key)
-
...
On IJsonArray, getters require an index instead of a key :
-
String getString(int index)
-
String getString(int index, String defaultValue)
-
...
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 :
-
IJsonObject merge(Map<String, Object> map, boolean clone)
Merges a Map<String, Object> into the IJsonObject.
You can specify if the added elements must be cloned or
not (in case some are IJsonObject or IJsonArray).
-
IJsonObject merge(IJsonObject jsonObj, boolean clone)
Merges an external IJsonObject into the IJsonObject.
You can specify if the added elements must be cloned or
not (in case some are IJsonObject or IJsonArray).
-
boolean isKeyExists(String key)
Does the IJsonObject contain the specified key?
-
IJsonObject remove(String key)
Removes an element using its key.
-
IJsonObject getJsonObjectOrEmpty(String key)
and
IJsonArray getJsonArrayOrEmpty(String key)
Gets an IJsonObject or IJsonArray or returns an empty
instance if it's not found. This allows you to try to get a deep element or use a default
value if this element is not found, without any potential NullPointerException. For
example :
// This won't throw any NPE, even if the "myArrayKey"
// array or its first element don't exist :
String value = obj.getJsonArrayOrEmpty("myArrayKey")
.getJsonObjectOrEmpty(0)
.getString("someKey", "defaultValue");
-
XXX getArrayFirstXXX(String key, String defaultValue)
For all types managed by IJsonObject, a getArrayFirstXXX(...)
method exists. With those methods, you can get an IJsonArray
using the specified key and returns the first element of the array.
This is useful in situations where you know an array only contains a single element :
// This :
String value = obj.getArrayFirstString("myArrayKey", "defaultValue")
// ... is a shortcut for :
String value = obj.getJsonArrayOrEmpty("myArrayKey").getString(0, "defaultValue")
-
String toJsonString(boolean pretty)
Converts the IJsonObject object to a Json string.
If pretty is true, the resulting Json will be formatted.
-
Map<String, Object> convertToPlainMap()
If you need to use the elements from an IJsonObject in a class that doesn't
know how to handle this type, you can convert it to a plain Map<String, Object>.
For example, Spincast uses this to pass the elements of an IJsonObject to the default Pebble
templating engine, as the variables to use to render a template. Pebble doesn't know about
IJsonObject objects but knows how to deal with
a plain Map<String, Object>.
Any IJsonObject element will be converted to
a Map<String, Object> and any IJsonArray element to
a List<Object>.
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 :
-
IJsonArray remove(int index)
Removes an element at the specified index.
-
int size()
Returns the size of the array.
-
String toJsonString(boolean pretty)
Converts the IJsonArray object to a Json string.
If pretty is true, the resulting Json will be formatted.
-
List<Object> convertToPlainList()
Converts the IJsonArray to a plain List<Object>.
Any IJsonObject element will be converted to
a Map<String, Object> and any IJsonArray element to
a List<Object>.
-
List<String> convertToStringList()
Converts the IJsonArray to a List<String>.
The toString() method will be called on any non null
element.
{#==========================================
Spincast Utilities
==========================================#}
Spincast Utilities
Spincast provides some generic utilities, accessible via the ISpincastUtils
interface:
-
void zipDirectory(File directoryToZip, File targetZipFile, boolean includeDirItself)
Zips a directory.
-
void zipExtract(File zipFile, File targetDir)
Extracts a .zip file to the specified directory.
-
String getMimeTypeFromMultipleSources(String responseContentTypeHeader, String resourcePath, String requestPath)
Gets the mime type using multiple sources of information.
-
String getMimeTypeFromPath(String path)
Gets the mime type from a path, using its extension.
-
String getMimeTypeFromExtension(String extension)
Gets the mime type from the extension.
-
Locale getLocaleBestMatchFromAcceptLanguageHeader(String acceptLanguageHeader)
Gets the best Locale to use given a "Accept-Language" HTTP header.
-
boolean isContentTypeToSkipGziping(String contentType)
Should the specified Content-Type be gzipped?
-
File getAppJarDirectory()
Returns the working directory: the directory
in which the executable .jar is located.
-
String getSpincastCurrentVersion()
Gets the current Spincast version.
-
String getCacheBusterCode()
The cache buster to use.
This should probably change each time
the application is restarted or at least redeployed.
It should also be in such a format that it's possible to
remove it from a given text.
This must be kept in sync with
removeCacheBusterCode!
-
String removeCacheBusterCodes(String text)
Removes the cache buster code occurences from the
given text.
Note that this won't simply remove the current
cache busting code, it will remove any valid cache busting code...
This is what we want since we don't want a client sending a request
containing an old cache busting code to break!
This must be kept in sync with
getCacheBusterCode!
{#==========================================
@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 :
-
5-8 : The constructor's job is only to
receive the dependencies.
-
10-14 : An
init() method is
also annotated with @Inject. This method will be called once the
object is fully constructed. This is a good place to do some initialization work!
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()!