Resource Abstraction
RIFE2 has abstracted the way resources are handled. Traditionally, resources are looked up through the classpath in Java, it can however be very useful to be able to freely change where resources are coming from.
The ResourceFinder
and ResourceWriter
interfaces handle searching,
retrieving and writing resources. RIFE2 provides a number of classes that have
already implemented these interfaces: DatabaseResources
, MemoryResources
,
ResourceFinderDirectories
and even a ResourceFinderGroup
that can combine a
collection of different resource finders into one group.
If you are looking up resources in your own application, consider using a
ResourceFinder
instead of the standard JDK's getClass().getResource(name)
,
this will provide you with a lot of flexibility down the line.
Here are some of the methods that are available through a ResourceFinder
instance:
getResource(String name); // retrieves resource for name
getContent(URL resource); // retrieves complete content of resource
getModificationTime(URL resource); // retrieves modification time of resource
useStream(URL resource, InputStreamUser user); // read the resource contents as a stream
The resources from the classpath are effectively static. Unless you write a custom classloader that you slot into the classloader hierarchical, you can't really change which resources are available.
RIFE2 provides a ResourceWriter
interface that complements the
ResourceFinder
interface. The DatabaseResources
and MemoryResources
classes implement both of these interfaces and thus allow the resources to be
changes at runtime.
Here are some of the methods that are available through a ResourceWriter
:
addResource(String name, String content); // add a resource with name and content
updateResource(String name, String content); // update a resource's content
removeResource(String name); // remove a resource
Pretty straightforward!
The template engine relies on a ResourceFinder
to get the content of any
template that you want to instantiate. By simply changing the resource finder
that is used by a template factory, you can store and retrieve templates from
a database, pull them from memory, or even generate or transform them on the fly
by implementing your own resource finder.
Let's set up the HTML template factory to use a MemoryResources
instance:
public class HelloResources extends Site {
// ...
public void setup() {
var resources = new MemoryResources();
TemplateFactory.HTML.setResourceFinder(new ResourceFinderGroup()
.add(ResourceFinderClasspath.instance())
.add(resources));
}
// ...
}
You can see that we also used a ResourceFinderGroup
that still uses the
classpath to search for resources and if none can be found there, they will be
retrieved from the MemoryResources
instance.
We can add a few templates as resources, for example:
public class HelloResources extends Site {
// ...
public void setup() {
// ... MemoryResources and TemplateFactory snippet from above
resources.addResource("hello.html", """
Hello <em><!--v name/--></em><br>
<a href="{{v route:bye/}}">Bye</a>""");
resources.addResource("bye.html", """
Bye <em><!--v name/--></em><br>
<a href="{{v route:hello/}}">Hello</a>""");
}
// ...
}
If we now use these templates in our web engine elements, they will be pulled from memory as expected:
public class HelloResources extends Site {
Route hello = get("/hello", c -> {
var t = c.template("hello");
t.setValue("name", "John");
c.print(t);
});
Route bye = get("/bye", c -> {
var t = c.template("bye");
t.setValue("name", "Jim");
c.print(t);
});
// ...
}
You can find this complete example in the RIFE2 repository.
The example above changes the TemplateFactory.HTML
instance that is used
throughout the entire application. You can however also create your own
TemplateFactory
that is set up with a different ResourceFinder
without
affecting anything else, if that is more appropriate for your use-case.
Next learn more about What is bld?