Orchid is built on top of Google Guice for dependency injection, and there really isn't anything that cannot be extended. A basic knowledge of Guice might be helpful for more advanced customization, but a basic explanation here will certainly be enough to get started writing basic plugins.
"Dependency Injection" (often shortened to DI) is a fancy term for a rather simple concept. To understand DI, lets look at a pretty standard example: logging. It is typical for an application to log information like the following snippet:
This is commonly known as the Factory Pattern, and it has some issues. The most obvious issue is that the LoggerExample is tied to exactly one type of Logger, so it becomes difficult to swap that implementation out. Sure the implementation can be changed within the factory body, this becomes much more difficult if the Logger required other conditions to be set up before it can be created.
Dependency Injection allows us to instead set up our class such that the Logger is passed directly to its constructor.
Now, the burden of creating the Logger is up to another class, and the LoggerExample's only job is to simply use the logger given to it. This makes it easier to test, but also gives consumers of the LoggerExample class information about what it does without looking at its source, namely that it is going to log something.
Now at its heart, DI is a concept and not tied to any particular framework. You could actually just set up all your classes to receive dependant objects in their only constructor and never need to set up a full framework, but this quickly gets too cumbersome as the project size grows and changes over time. So while it is possible to implement DI without a full framework, it is much more common to use a framework like Spring or Guice to do the hard work for you.
That being said, Orchid is based on Guice, which has several advantages over Spring:
- Guice is smaller and more lightweight than Spring
- Guice has a much quicker startup time
- Spring is tailored very heavily for web applications and the Servlet lifecycle, but Guice is designed to work for any application. Since Orchid is not a web application, much of what is included in Spring by default is not needed.
Guice Module Registration
Nearly everything in Orchid is set up using dependency injection similar to as shown above, using Guice as the DI framework. But in order for Guice to know how to construct the dependencies that are injected, we need to tell Guice what classes can be used in our dependency graph. We do this by creating modules which instruct Guice on how to set this up for us.
When Orchid is first started, it scans the classpath, looking for all classes that implement the Guice
interface, collect them all together, and then pass them all to Guice for the actual injection. So simply including a
dependency in your
build.gradle which has a Module in it is enough for Orchid is add it to the Orchid build.
You should generally have your modules extend
OrchidModule rather than using the default Guice
because it offers a few extra conveniences more useful for Orchid, and is itself a child class of
being said, Orchid auto-registers any class that implements the Guice
Module interface, so either can be used just as
Looking at the module above, you'll need to override the
configure(), and in that method you will set up your
bindings. There are two types of bindings you will commonly use for Orchid: binding concrete implementations to abstract
classes or interfaces, which is what is how Guice is typically used, and adding implementations to sets. The second is
by far the most commonly used binding in Orchid, and if you are building your own extendible interfaces, should almost
always be the preferred way to set up your bindings. Both of these are described below.
bind() method takes an abstract class, and assigns a specific implementation class to it. Instances of this class
should always request injection of the abstract class, so that Guice can handle providing the concrete implementation,
changing it if necessary.
If you are the one creating both the abstract and implementation classes, you should avoid setting up the association
within the module itself. Instead of using
bind(A.class).to(B.class), you can instead use the
annotation on the abstract class to set up an equivalent binding.
This method of using the
@ImplementedBy annotation has several advantages over the module binding. First, it makes it
easier to understand the intended relationship in the source code for both classes, and both the interface and the
implementation point directly to one another. In addition, bindings within a module have a higher priority than the
annotation, which allows another plugin to extend yours by creating a module to register a different interface. Guice
will complain if two modules try to create a binding to the same interface, but it will happily accept both a module
and annotation binding, treating the annotation as the "default".
The most common types of bindings in Orchid are multi-bindings, or more specifically set bindings. What this means, is
that rather than having Guice inject a single instance, you can have it inject a
Set<> of some type, where each
instance in that set is a unique type. This is how we can get a plugin architecture in Orchid, as most bindings are
bound as sets rather than single instances, and you can contribute new implementations to any of those sets seamlessly.
OrchidModule class adds the
addToSet() method, where the first parameter is the abstract class or interface
class, followed by a vararg list of implementation classes. Orchid will also keep track of all sets bound in this way so
that you can easily see every possible class type which can be extended through multi-bindings. You can see all
available set types from the Admin Panel.
Some common examples of classes that are bound through sets are:
- Component Types
- Menu Item Types
- Template Tags
In some cases, you may wish for a particular module to not be auto-registered, even though it is on the classpath. A
good example is the
JavadocModule from the Orchid Javadoc plugin. This plugin should
only register its bindings when Orchid has been started from the Javadoc tool, rather than Orchid's normal main class,
and it manually adds the
For these cases, you can add the
@IgnoreModule annotation to the module class to have it skipped during
Understanding the Orchid Content Model
Generators are at the very core of how Orchid is so flexible. While most static site generators define rules and models around how content is discovered and rendered, Orchid offloads this entire process to plugins, and instead build a powerful framework for making it incredibly easy to define these rules yourself. Some existing tools are pretty rigid, like Jekyll's strong base of blogging content, while others are very flexible, like Hugo's custom taxonomies, but they are all limited to that one way of doing things. Orchid, on the other hand, is able to capture the essence of all these tools and bring all their content models into one site, even opening the content for one plugin to build off the content of another, giving you unlimited possibilities.
Orchid uses a unique way to create content that makes it easy to work with, but also gives a guarantee that the data you need is available when you need it. To do this, it uses Generators, and employs a separate indexing and generating phase for each generator, which will be described in the next couple pages. But the big idea here is that Orchid allows plugins to load their data before any pages are rendered, so each rendered page can contain data from any other generator, knowing that it will always be there when you need it.
This allows us to do some really cool things like building pages out of smaller Components, creating custom menus with menu items dynamically populated from the pages that Orchid has indexed, and creating strong links among pages that adapt to changing URL structures, even between different plugins. In addition, this allows us to build powerful content models that each work in isolation, but work together really well when combined in the rendered templates.
Available Content Models
Orchid was created to take the best parts of many popular static site generators and similar tools, and bring them into one place. Below is a list of some well-known tools whose content model has inspired some official Orchid plugins, along with the Orchid plugins that came from this inspiration.