In runtime Cayenne is accessed via
org.apache.cayenne.configuration.server.ServerRuntime
. ServerRuntime is
created simply by calling a
constructor:
ServerRuntime runtime = new ServerRuntime("com/example/cayenne-project.xml");
The parameter you pass to the constructor is a location of the main project file. Location is a '/'-separated path (same path separator is used on UNIX and Windows) that is resolved relative to the application classpath. The project file can be placed in the root package or in a subpackage (e.g. in the code above it is in "com/example" subpackage).
ServerRuntime encapsulates a single Cayenne stack. Most applications will just have one ServerRuntime using it to create as many ObjectContexts as needed, access the Dependency Injection (DI) container and work with other Cayenne features. Internally ServerRuntime is just a thin wrapper around the DI container. Detailed features of the container are discussed in "Customizing Cayenne Runtime" chapter. Here we'll just show an example of how an application might replace a default implementation of a built-in Cayenne service (in this case - QueryCache) with a different class:
public class MyExtensionsModule implements Module { public void configure(Binder binder) { binder.bind(QueryCache.class).to(EhCacheQueryCache.class); } }
Module extensions = new MyExtensionsModule(); ServerRuntime runtime = new ServerRuntime("com/example/cayenne-project.xml", extensions);
It is a good idea to shut down the runtime when it is no longer needed, usually before the application itself is shutdown:
runtime.shutdown();
When a runtime object has the same scope as the application, this may not be always necessary, however in some cases it is essential, and is generally considered a good practice. E.g. in a web container hot redeploy of a webapp will cause resource leaks and eventual OutOfMemoryError if the application fails to shutdown CayenneRuntime.
ServerRuntime requires at least one mapping project to run. But it can also take multiple projects and merge them together in a single configuration. This way different parts of a database can be mapped independenlty from each other (even by different software providers), and combined in runtime when assembling an application. Doing it is as easy as passing multiple project locations to ServerRuntime constructor:
ServerRuntime runtime = new ServerRuntime(new String[] { "com/example/cayenne-project.xml", "org/foo/cayenne-library1.xml", "org/foo/cayenne-library2.xml" } );
When the projects are merged, the following rules are applied:
The order of projects matters during merge. If there are two conflicting metadata objects belonging to two projects, an object from the last project takes precedence over the object from the first one. This makes possible to override pieces of metadata. This is also similar to how DI modules are merged in Cayenne.
Runtime DataDomain name is set to the name of the last project in the list.
Runtime DataDomain properties are the same as the properties of the last project in the list. I.e. properties are not merged to avoid invalid combinations and unexpected runtime behavior.
If there are two or more DataMaps with the same name, only one DataMap is used in the merged project, the rest are discarded. Same precedence rules apply - DataMap from the project with the highest index in the project list overrides all other DataMaps with the same name.
If there are two or more DataNodes with the same name, only one DataNodes is used in the merged project, the rest are discarded. DataNode coming from project with the highest index in the project list is chosen per precedence rule above.
There is a notion of "default" DataNode. After the merge if any DataMaps
are not explicitly linked to DataNodes, their queries will be executed via a
default DataNode. This makes it possible to build mapping "libraries" that
are only associated with a specific database in runtime. If there's only one
DataNode in the merged project, it will be automatically chosen as default.
A possible way to explicitly designate a specific node as default is to
override DataDomainProvider.createAndInitDataDomain()
.
Web applications can use a variety of mechanisms to configure and start the "services" they need, Cayenne being one of such services. Configuration can be done within standard Servlet specification objects like Servlets, Filters, or ServletContextListeners, or can use Spring, JEE CDI, etc. This is a user's architectural choice and Cayenne is agnostic to it and will happily work in any environment. As described above, all that is needed is to create an instance of ServerRuntime somewhere and provide the application code with means to access it. And shut it down when the application ends to avoid container leaks.
Still Cayenne includes a piece of web app configuration code that can assist in quickly setting up simple Cayenne-enabled web applications. We are talking about CayenneFilter. It is declared in web.xml:
<web-app> ... <filter> <filter-name>cayenne-project</filter-name> <filter-class>org.apache.cayenne.configuration.web.CayenneFilter</filter-class> </filter> <filter-mapping> <filter-name>cayenne-project</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ... </web-app>
When started by the web container, it creates a instance of ServerRuntime and stores it in the ServletContext. Note that the name of Cayenne XML project file is derived from the "filter-name". In the example above CayenneFilter will look for an XML file "cayenne-project.xml". This can be overridden with "configuration-location" init parameter.
When the application runs, all HTTP requests matching the filter url-pattern will have access to a session-scoped ObjectContext like this:
ObjectContext context = BaseContext.getThreadObjectContext();
Of course the ObjectContext scope, and other behavior of the Cayenne runtime can be customized via dependency injection. For this another filter init parameter called "extra-modules" is used. "extra-modules" is a comma or space-separated list of class names, with each class implementing Module interface. These optional custom modules are loaded after the the standard ones, which allows users to override all standard definitions.
For those interested in the DI container contents of the runtime created by CayenneFilter,
it is the same ServerRuntime as would've been created by other means, but with an extra
org.apache.cayenne.configuration.web.WebModule
module that provides
org.apache.cayenne.configuration.web.RequestHandler
service. This is
the service to override in the custom modules if you need to provide a different
ObjectContext scope, etc.
You should not think of CayenneFilter as the only way to start and use Cayenne in a web application. In fact CayenneFilter is entirely optional. Use it if you don't have any special design for application service management. If you do, simply integrate Cayenne into that design.