-
-
Notifications
You must be signed in to change notification settings - Fork 100
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 ordered in a graph, 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.
There are two new SPI interfaces, Initializable
and Destroyable
. They define #init(Workspace)
and #destroy(Workspace)
respectively. They can be used to initialize things that need to be done statically on a JVM global basis, such as initialization of the proxy settings (which need to be VM global).
Their use is somewhat discouraged (use proper resource managers where possible!), but sometimes unavoidable (like the proxy settings) or hard to fix (like the CRS subsystem which needs a rewrite anyway).
Initializable
s are initialized upon workspace startup (before any resources are initialized), Destroyable
s are destroyed upon workspace destruction (after all resources are destroyed).
Since there was no need yet, there is no dependency concept for these extension points and they may be executed in any random order.
Initializable
and Destroyable
can be used independently from each other, the SPI extension points are org.deegree.workspace.Initializable
and org.deegree.workspace.Destroyable
respectively.
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 can be implemented, the third subsection describes how a new resource type can be implemented. The last subsection shows you how to use the ResourceGraph
.
If you're looking for random bits check out the WorkspaceUtils
class (in the org.deegree.workspace
package). While it contains some useful shortcut methods to achieve simple tasks, its code also demonstrates how to manually work with the workspace.
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 (module deegree-featurestore-simplesql).
You typically need at least four new classes to implement a new resource that automatically plugs into the workspace:
- 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 needs to be done here. Don't forget to add file META-INF/services/my.resource.provider.package.MyResourceProvider
with the fully qualified class name of the provider in it (the workspace learns about available providers using the classic Java SPI mechanism).
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 and add your dependencies to the dependencies
field. If you have dependencies that need to be initialized before your resource but are not crucial, you can add them to the softDependencies
field. This will make sure they are initialized before your resource, but allows your resource to be initialized even if they fail. Make sure to check for null
before using them!
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 defined by the respective resource type (e.g. FeatureStore
) and your concrete implementation. Just make sure the #getMetadata
returns the proper metadata object (passed down during initialization), and you clean up after yourself in #destroy
.
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 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 (the workspace finds out about resource types via Java SPI).
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 initialized automatically.
If you have some metadata objects and want to see how they are ordered dependency wise, you can construct a ResourceGraph
:
ResourceGraph graph = new ResourceGraph();
You can also construct a graph with a bunch of ResourceMetadata
objects:
List<ResourceMetadata<? extends Resource>> list = new ArrayList<ResourceMetadata<?>>();
list.add( md1 );
list.add( md2 );
// ... add more
ResourceGraph graph = new ResourceGraph( list );
You can then examine the graph, or add more nodes:
ResourceMetadata<? extends Resource> metadata = ...;
graph.insertNode( metadata );
ResourceNode<? extends Resource> node = graph.getNode( metadata.getIdentifier() );
A ResourceNode
provides access to its dependencies, soft dependencies and nodes which directly depend on this resource, the dependents.
A ResourceGraph
can be flattened by using the #toSortedList
method:
List<ResourceMetadata<? extends Resource>> sorted = graph.toSortedList();
This gives you an ordered list, where resources without dependencies are in the front. Every metadata entry is always preceded by its dependencies.
About the further development of the workspace, see WorkspaceNG.