-
-
Notifications
You must be signed in to change notification settings - Fork 99
Workspace
This page is the central entry point for documentation about the deegree workspace concept and implementation.
The old workspace concept had a couple of shortcomings, which I'll describe here to give an understanding why a new implementation has been started.
The workspace has a concept of dependencies, but dependencies are only possible on a resource type level. At first glance, this is sufficient in most cases, but not all.
The first resource we came across where this was not sufficient any more was the caching tile store, which naturally depends on another tile store.
Thinking about it, a tile store implementation that automatically tiles a layer resource on the fly/whatever would be a natural thing. But since there's also a tile layer implementation, the layer subsystem already depends on the tile subsystem, introducing a dependency the other way round would introduce a cycle.
Restarting a single resource is possible, but often has unknown/bad side effects on other resources. So if you restart your feature store (possibly changing the configuration) affects eg. the WFS, some feature layer, the WMS...
The only workable solution has been to just restart the whole workspace. While that's sufficient in many cases, it can be slow and time consuming, especially if many resources are configured.
If the exact dependency chains between resources were known, only affected resources could be restarted.
Some resource managers do things in a static context. While this is often sufficient in a webapp (where only a single workspace is active anyway), it's still bad practice and should be avoided. Looking at the infamous ConnectionManager, a rewrite is badly needed.
The workspace is tightly bound to the XML formats which are used to configure resources. While this is not necessarily a bad thing, other scenarios still seem useful, where configurations are created by some other means (such as reading the config from a database). This is just not possible to do with the current workspace concepts.
So, let's have a look at the basics. The workspace revolves around resources. A resource is an instance of a specific class of object deegree works with. Example: a feature store is a resource, a WFS is a resource, a layer is a resource, a JDBC connection is a resource.
Resources are grouped together by their type. So all feature stores are in a group, all services are in a group, all layers, all JDBC connections etc. The workspace is responsible to manage these resources, and provide access to them.
The workspace manages resources, and provides access to them. That means that the lifecycle of resources is controlled by the workspace.
Typically your interaction with the workspace will be to initialize it, and to access resources contained within.
Resources are identified by ResourceIdentifier
objects. This is a two part or qualified identifier, consisting of a string ID (usually the basename of the .xml
configuration file) and a provider class.
We'll have a look at the provider classes later.
In order to understand how the workspace works internally, you'll need to understand the lifecycle a resource goes through. We've got a couple of phases:
- scanning
- preparing
- building
- initializing
In the scanning phase, the workspace finds resources. This results in a bunch of ResourceMetadata
objects. These metadata objects are uninitialized.
In the preparing phase, which operates on ResourceMetadata
objects, the resources determine what they need to be built. This includes finding out what other resources they depend on. This results in the ResourceMetadata
to be initialized, and a bunch of ResourceBuilder
objects. After this phase the ResourceMetadata
can be sorted, taking into account their dependencies.
In the building phase, the ResourceBuilder
objects are used actually build Resource
objects, the result is a bunch of uninitialized Resource
objects.
In the initialization phase, the Resource
objects are being initialized. After this, they can be properly used.
This section introduces a couple of interfaces, and explains what implementations are responsible for.
Some interfaces have been introduced above in the lifecycle section.
ResourceLocation
objects are an abstraction of where the configuration files live. Instead of URL
or File
objects they are used to obtain the actual configuration file content. This allows for other implementations that eg. pull the files off the net or so.
ResourceManager
objects are responsible for initially creating ResourceMetadata
objects during the scanning phase, and for knowing which ResourceProvider
implementations are available.
ResourceProvider
objects are used by the manager to actually create a ResourceMetadata
object for a resource. For each type of resource, there must be an abstract class or interface that all concrete providers implement. This serves as an SPI extension point (so the resource manager knows who can create metadata objects for a given location). The abstract ResourceProvider
for a specific type of resource is also used to qualify the ResourceIdentfier
objects. While the providers are usually not very big, they're still very central to how the workspace works.
This section describes how you can work with the workspace. The first subsection shows how you can work with resources. The second subsection describes how a new resource type can be implemented, the third subsection describes how a new resource can be implemented.
First, you'll need a workspace:
Workspace workspace = new DefaultWorkspace( "/path/to/workspace" );
Then you'll need to initialize the resources:
workspace.initAll();
Now you're ready to go:
FeatureStore fs = workspace.getResource( NewFeatureStoreManager.class, "myfeature" );
ConnectionProvider prov = workspace.getResource( ConnectionProviderProvider.class, "postgresonsecundum" );
As you can see, the workspace doesn't explicitly want a ResourceIdentifier
, but still requests the two identifier parts. Just give him the base provider class of the resource you'd like, and the actual ID.
To implement a new resource, it's advisable to browse through existing implementations of the same resource, e.g. if you want to implement a new feature store, check out the SimpleSQLFeatureStore implementation.
You typically need at least four new classes to implement a new resource:
- A
ResourceProvider
implementation: Invoked by theWorkspace
to createResourceMetadata
objects - A
ResourceMetadata
implementation: Provides information on the required dependencies (depending on the configuration) - A
ResourceBuilder
for the newResource
: Creates aResource
instances from a JAXB object (the XML configuration) - The actual
Resource
class that implements the respectiveResource
subinterface (e.g. FeatureStore)
The first is the implementation of your ResourceProvider
. This should be easy enough. When implementing #createFromLocation
you'll notice that you'll need a ResourceMetadata
implementation. There's really not much besides the metadata instantiation that usually needs to be done here.
The ResourceMetadata
implementation should also not provide a lot of trouble. The only thing to keep in mind is that you'll need to figure out the dependencies of your resource during #prepare
. In order to make life easy, just extend the AbstractResourceMetadata
class, just add your dependencies to the dependencies
field.
While implementing #prepare
, you'll realize you also need a ResourceBuilder
. At this stage, you'll probably still have the JAXB parsed configuration object. The idea is that you deconstruct the JAXB stuff and construct a proper Resource
object during #build
. Make sure you pass the ResourceMetadata
object to the resource.
Last but not least, of course you'll need a Resource
implementation. What that means exactly, is up to you of course. Just make sure the #getMetadata
returns the proper metadata object (passed down during initialization), and you clean up after yourself in #destroy
.
Don't forget to add a META-INF/services/my.resource.provider.package.MyResourceProvider
with your provider fully qualified class name in it!
To implement a new resource type, it's generally advisable to have a look at existing code. For a new resource type, you typically need two classes, a new ResourceManager
and a new abstract ResourceProvider
.
In order to make life simple, just extend the DefaultResourceManager
. Add a default constructor, in which you call the super constructor like this:
public class MyResourceManager extends DefaultResourceManager<MyResource> {
public MyResourceManager() {
super( new DefaultResourceManagerMetadata<MyResource>( MyResourceProvider.class, "my resources",
"datasources/myresources" ) );
}
}
Extend the AbstractResourceProvider
to have the marker provider class:
public abstract class MyResourceProvider extends AbstractResourceProvider<MyResource> {
}
Don't forget to actually add your Resource
interface:
public interface MyResource extends Resource {
// add fancy methods
}
Then don't forget to add a META-INF/services/org.deegree.workspace.ResourceManager
file containing your fully qualified ResourceManager
class name.
That's all you need! If you have implementations of your resource provider on the class path, they'll get loaded automatically, and any resources your provider wants to handle will be initialzed automatically.