![]() ![]() ![]() ![]() ![]() ![]() |
This chapter provides information about creating an SPI implementation. It contains the following sections:
When the VCR needs to access a specific repository from the set of application repository configurations, the VCR loads and creates an instance of the configured SPI implementation class, which implements the VCR SPI Repository interface. The VCR invokes methods on the repository implementation to obtain objects, such as a Ticket
, which implement other VCR SPI interfaces.
The VCR invokes methods on the Repository and Ticket implementations to query the SPI implementation for its capabilities. (Capabilities are what operations the implementation supports.) The VCR then invokes methods to retrieve the operation interfaces, such as NodeOpsV1, that the SPI exposes. Finally, the VCR invokes methods on the operation interfaces.
The two primary classes for an SPI implementation are:
The Repository
class is instantiated directly by the VCR. This class provides anonymous repository access. For example, an SPI implementation can report its version and other basic information. Most importantly, this is the entry point for authenticating to an SPI implementation to obtain a Ticket. Only authenticated users can obtain a Ticket; the Ticket provides access to the important repository operations.
The Ticket
class provides authorized repository access, and provides access to the authorized operations interfaces such as NodeOpsV1
. Only authenticated users can obtain a Ticket
; the Ticket
provides access to the important repository operations. This class is instantiated by Repository.connect(…)
.
The Repository
can optionally expose additional authorized ticket operations interfaces, such as NodeOpsV1
, SearchOpsV1
, via its implementation of getAllInterfaces()
and getInterface()
.
The general flow at runtime is:
Repository
class.Repository
object.Repository
object.Repository.getAllInterfaces()
to retrieve all repository interfaces.Repository.connect()
to obtain a Ticket
. Ticket.getAllInterfaces()
to retrieve all ticket interfaces.
Use the following design guidelines when creating an SPI implementation:
There are several reasons for this. Primarily, because in a cluster scenario or multiple enterprise applications on the same server, changes to external system data should be persisted immediately. This allows the data to appear “live” regardless from which server and enterprise application the data is accessed.
Nodes
, Properties
, Values
, ObjectClasses
, PropertyDefinitions
, and PropertyChoices
). The VCR and client code own these objects, which they can modify. For example, the VCR may change the node path before returning the node to the client.A safe approach is to cache the objects, then return a clone of these objects, which the SPI implementation will not have references to.
Because the data object types are shared between the SPI implementation, VCR, and client code, not all methods on these data objects are appropriate for each type of caller. (See the
WebLogic Portal Javadoc for clarification.) For example, new data objects, such as Properties
passed when creating a Node
from the VCR to the SPI implementation do not have UUID on the PropertyID
. This happens because the UUID is assigned by the SPI implementation and the new object has not yet been persisted by the SPI implementation. The UUID is present only for data objects retrieved or returned by the SPI.
Repository.setName()
and Repository.setProperties()
methods before it calls the connect()
method.Each Repository instance corresponds to a single repository configuration for a single enterprise application and for use by a single user. At runtime, the VCR can create many instances of Repositories for use by numerous current WebLogic Portal users. For this reason they should be relatively lightweight objects, as hundreds of these objects may exist.
In an enterprise application for a given SPI implementation, there may be multiple active repository configurations. For example, three repository configurations may exist for an application that the VCR is managing, and each repository will have its own configuration data and its own Repository instance that is created at runtime.
RepositoryException
. Several subclasses of RepositoryException
are defined, and these should be used when a good match exists. For example, if the SPI implementation does not support an operation, it should throw a UnsupportedRepositoryOperationException
.Ticket
object is cached and re-used by the VCR. Generally, it should be stateless for concurrency reasons. The VCR associates the Ticket
with a user on a HTTPSession
. If multiple requests on the same HTTPSession
arrive for the same repository, multiple operations on the same Ticket
object can be performed simultaneously. A stateless design will avoid issues in this situation. –
When the node’s properties (and values) are loaded, the binary property inputstream
can be returned as null to boost performance.
To create a basic SPI implementation:
com.bea.content.spi.flexspi.Repository
. This class is a required interface that must be implemented. It is included with WebLogic Portal.
setProperties()
to store the repository configuration properties passed by the VCR. Store in a local variable.setName()
to store the repository configuration name passed by the VCR. Store in a local variable.getProperties()
and getName()
to read the local variables. getDescription()
to report the repository description data.
This is essentially a Properties
bucket, with some well-defined keys in SPIDescriptionKeys
. It includes the vendor, version, and so on.
getRepositoryDefinedCapabilities()
to report which custom capabilities the SPI implementation supports.
For a basic SPI implementation, just return Collections.emptySet()
.
getAllInterfaces()
to return all repository (not ticket
) operations interfaces.
These are optional interfaces, such as RepositoryConfigOpsV1
, that a Repository can implement.
For a bare-bones SPI implementation, just return an empty HashMap
.
getInterface( String interfaceName )
to be consistent with getAllInterfaces()
. For a bare-bones SPI implementation, just return null.
getCapabilitySupport( Set<ICapabilityDefinition> )
to report which feature capabilities this SPI implementation supports. It also report which method capabilities the repository supports across the optional repository operation interfaces.
connect( Credentials )
and Connect( String username, String password )
to allow a caller to obtain a ticket.
connect( username, password )
is called if a username and password are available (generally from the repository configuration data.)
connect( credentials )
is called if no username/password are available. The credentials includes the caller’s implicit identity.
For a basic SPI implementation, create and return a new Ticket
instance. More advanced implementations may authenticate the credentials, and only return a Ticket
instance if successful.
Repository
operation interfaces, such as RepositoryConfigOpsV1
.
For any repository operation interfaces returned by Repository.getAllInterfaces()
:
Any methods that are not implemented should throw an UnsupportedRepositoryOperationException
and the getCapabilitySupport()
method should not report this method as supported.
Repository.getAllInterfaces()
and getInterface()
to return the interface implementation.Repository.getCapabilitySupport()
to report the status (supported or unsupported) of each method on the repository operation interface. com.bea.content.spi.flexspi.Ticket
(supplied with WebLogic Portal)getAllInterfaces()
to return all ticket operations interfaces. These are optional interfaces, such as NodeOpsV1
, which a Ticket
can implement. For a basic SPI implementation, return an empty HashMap
.getInterface( String interfaceName )
to be consistent with getAllInterfaces()
. For a basic SPI implementation, just return null.Implement getCapabilitySupport( Set<ICapabilityDefinition> )
to report which method capabilities this ticket supports across the optional ticket operation interfaces.Ticket
operation interfaces, such as NodeOpsV1
.
For any ticket optional interfaces returned by Ticket.getAllInterfaces()
:
Any methods that are not implemented should throw an UnsupportedRepositoryOperationException
and the getCapabilitySupport()
method should not report this method as supported.
Ticket.getAllInterfaces()
and getInterface()
to return the interface implementation.Ticket.getCapabilitySupport()
to report the status (supported or unsupported) of each method on the ticket operation interface. Listing 4-1shows a simple SPI Repository code example of a Repository with the following limitations:
RepositoryConfigOpsV1
, and therefore does not support any method capabilities. import com.bea.content.spi.flexspi.Repository;
import com.bea.content.spi.flexspi.Ticket;
import com.bea.content.spi.flexspi.common.capability.ICapabilityDefinition;
import com.bea.content.spi.flexspi.common.capability.CapabilityLevel;
import com.bea.content.spi.flexspi.common.capability.FeatureCapabilityDefinition;
import com.bea.content.spi.flexspi.common.ISPIMarker;
import com.bea.content.spi.flexspi.common.SPIRepositoryInterfaces;
import com.bea.content.spi.flexspi.common.SPIDescriptionKeys;
import com.bea.content.capability.NodeFeatureCapability;
import com.bea.content.*;
import java.util.*;
public class FlexRepositoryImpl implements Repository
{
// VCR configures these before connect() is called
private String repositoryName;
private Properties props;
// the description key/values
private Map<String, String> descMap = new HashMap<String, String>();
public FlexRepositoryImpl()
{
// SPI implementation MUST have a public default ctor
//standard keys
descMap.put(SPIDescriptionKeys.VENDOR_KEY,
"Some third party vendor");
descMap.put(SPIDescriptionKeys. VERSION_KEY, "0.1.1");
descMap.put(SPIDescriptionKeys. DESCRIPTION_KEY, "Simple SPI");
//custom keys can also be added
descMap.put("InternalVersion", "Build 31141");
}
public Ticket connect(Credentials credentials)
throws AuthenticationException, RepositoryException
{
// this SPI does not perform authentication
return new FlexTicketImpl(this);
}
public Ticket connect(String username, String password)
throws AuthenticationException, RepositoryException
{
// this SPI does not perform authentication
return new FlexTicketImpl(this);
}
public String getName() {
return repositoryName;
}
public void setName(String name) {
repositoryName = name;
}
public Properties getProperties() {
return props;
}
public void setProperties(Properties properties) {
props = properties;
}
public Set<ICapabilityDefinition> getRepositoryDefinedCapabilities() {
return Collections.emptySet();
}
public Map<String, String> getDescription() {
return descMap;
}
public Map<String, ISPIMarker> getAllInterfaces() {
// no repository operation interfaces
return new HashMap<String,ISPIMarker>();
}
public ISPIMarker getInterface(String interfaceName) {
// no repository operation interfaces
return null;
}
public Map<ICapabilityDefinition, CapabilityLevel>
getCapabilitySupport(Set<ICapabilityDefinition> capabilities)
{
HashMap<ICapabilityDefinition, CapabilityLevel> capMap
=new HashMap<ICapabilityDefinition, CapabilityLevel>();
// start out with everything not supported; we will mark
// individual feature capabilities as supported.
for (ICapabilityDefinition capDef : capabilities) {
capMap.put(capDef, CapabilityLevel.NotSupported);
}
// here we would override the unsupported value if we supported anything
// but we don’t…
// everything unsupported
return capMap;
}
}
Listing 4-2 shows a simple SPI Ticket code example of a Ticket that does not support any optional ticket operation interfaces, such as NodeOpsV1
and SearchOpsV1
, and therefore does not support any method capabilities
import com.bea.content.spi.flexspi.Ticket;
import com.bea.content.spi.flexspi.Repository;
import com.bea.content.spi.flexspi.common.ISPIMarker;
import com.bea.content.spi.flexspi.common.SPITicketInterfaces;
import com.bea.content.spi.flexspi.common.capability.ICapabilityDefinition;
import com.bea.content.spi.flexspi.common.capability.CapabilityLevel;
import com.bea.content.spi.flexspi.common.capability.MethodCapabilityDefinition;
import java.util.*;
public class FlexTicketImpl implements Ticket
{
Repository repository; // the repository this ticket was created from
//1=flex interface name, 2=flex interface object
private Map<String, ISPIMarker> advertisedInterfaces;
public FlexTicketImpl(Repository repository) {
this.repository = repository;
advertisedInterfaces= new HashMap<String,ISPIMarker>();
// no interfaces yet
}
public Map<String, ISPIMarker> getAllInterfaces() {
return advertisedInterfaces;
}
public ISPIMarker getInterface(String interfaceName) {
return advertisedInterfaces.get(interfaceName);
}
public Map<ICapabilityDefinition, CapabilityLevel>
getCapabilitySupport(Set<ICapabilityDefinition> capabilities)
{
// no interfaces, no methods, no capabilities; everything is unsupported
HashMap<ICapabilityDefinition, CapabilityLevel> capMap
=new HashMap<ICapabilityDefinition, CapabilityLevel>();
// start out with everything not supported; we will mark
// individual feature capabilities as supported.
for (ICapabilityDefinition capDef : capabilities) {
capMap.put(capDef, CapabilityLevel.NotSupported);
}
// here we would override the unsupported value if we supported anything
// but we don’t…
// everything unsupported
return capMap;
}
}
You use optional SPI interfaces to expose nodes and types to the VCR. Generally, client code has certain assumptions about the capabilities of the repositories the code runs against, such as read-only access to nodes. As the VCR delegates to the repository, the client code presumes that certain VCR methods work properly.
The optional PSPI interfaces are:
RepositoryConfigOpsV1
provides repository callbacks as repository configurations that are modified by VCR clients (generally, administrators). For example, createRepository(…)
, updateRepository(…)
, and removeRepository(…)
.
To expose an optional SPI interface such as NodeOpsV1
.
For example MyNodeOps
implements NodeOpsV1
. Generally, you should make this a light-weight class, as many objects may be created.
Note: | Any methods that are not implemented should throw an UnsupportedRepositoryOperationException . Additionally, the Ticket.getCapabilitySupport() method should report this method as not supported. |
Modify Ticket.getAllInterfaces()
and getInterface()
to return the interface implementation.
To reduce object creation, create the interfaces when the ticket is created, hold onto them, and then return them in getAllInterfaces()
and getInterface()
. For example:
private Map<String,ISPIMarker> ifaces;
public FlexTicketImpl( Repository repository ) {
this.repository= repository;
//init interfaces
ifaces= new HashMap<String,ISPIMarker>();
ifaces.put(SPITicketInterfaces.NODE_OPS_V1,
new MyNodeOps(…);
. . .
}
public Map<String, ISPIMarker> getAllInterfaces() {
return ifaces;
}
public ISPIMarker getInterface( String ifaceName ) {
return ifaces.get( ifaceName );
}
Ticket.getCapabilitySupport()
to report the status (supported or unsupported) of each method on all the ticket operation interfaces. For example:public Map<ICapabilityDefinition, CapabilityLevel>
getCapabilitySupport(Set<ICapabilityDefinition> capabilities)
{
Map<ICapabilityDefinition, CapabilityLevel> capMap
=new HashMap<ICapabilityDefinition, CapabilityLevel>();
// start out with everything not supported; we will mark
// individual feature capabilities as supported.
for (ICapabilityDefinition capDef : capabilities) {
capMap.put(capDef, CapabilityLevel.NotSupported);
}
// now override the unsupported values where it makes sense
final String[] supportedNodeOpsMethodNames = new String[] {
NodeOpsV1.MethodName.getNodeChildren.toString(),
NodeOpsV1.MethodName.getNodeChildrenAsNodeIds.toString(),
NodeOpsV1.MethodName.getNodesWithIds.toString(),
NodeOpsV1.MethodName.getNodeWithId.toString(),
NodeOpsV1.MethodName.getNodeWithPath.toString(),
};
for (String methodName : supportedNodeOpsMethodNames) {
ICapabilityDefinition capDef
= new MethodCapabilityDefinition(
SPITicketInterfaces.NODE_OPS, methodName
);
if (capabilities.contains(capDef)) {
capMap.put(capDef, CapabilityLevel.FullySupported);
}
}
return capMap;
}
Optionally, the SPI implementation can include the ability to sort and/or filter results. The collections of items returned by the SPI are returned in a QueryResult
object (including an ordered list of results) and a QueryCriteria
object that describes how the returned collection is sorted and filtered (if at all). For instance, a QueryResult<Node>
may contains a collection of nodes that are both sorted and filtered.
Many of the methods that return results collections take a QueryCriteria
parameter. This parameter allows the caller to indicate how results should be sorted or filtered or both (if possible). For example, the caller may request that the results be sorted by name. At present, the caller can specify only one sort criteria and one filter criteria.
The SortCriteria
contains a property (criteria) and an ascending or descending flag, such as name ascending
.
The FilterCriteria
contains a property (criteria), a FilterMethod
operand, such as unfiltered, equals, not equals, contains, not contains, begins with, ends with, greater than, and less than, and a value. For example, name contains 'foo'
.
The sort and filter criteria set of properties are related to the JavaBean properties on the data objects being used. For example, Node
contains a JavaBean property for name
because it has a getName()
method; for createdDate
because it has a getCreatedDate()
method; and so on.
Native sorting and filtering performs best. The VCR also supports mechanisms for sorting and filtering in-memory if the SPI implementation is unable to service a sort or filter request. Client code (and the VCR) can ask the SPI which properties it can natively sort and filter.
The SPI implementation reports its sorting and filtering capabilities (the properties it can sort and filter on) to the VCR for objects on an interface by implementing methods such as NodeOpsV1.getNativeSortableProperties()
and NodeOpsV1.getNativeFilterableProperties()
. For example, the SPI may report that it can sort nodes by name
and createDate
properties.
The sorting and filtering capability reporting is currently done at the interface granularity. In other words, a given interface such as NodeOpsV1
has a primary data object. The primary data object, a Node
for example, reports the capabilities for sorting and filtering across the results collection methods in the interface.
Tip: | If the SPI does not support native sorting or filtering, it should return an empty set, such as Collections.emptySet() rather than null. |
The SPI implementation may receive a QueryCriteria
parameter that requests sorting or filtering capabilities beyond what it can support. If this happens, the SPI implementation should not throw an exception. Instead the SPI implementation should do its best to report how the results are currently sorted and filtered. For example, the SPI implementation should create an unsorted and/or unfiltered QueryResult
to express what it was unable to sort and/or filter. If the SPI implementation can perform one of the requests, it should do so and report the results appropriately. For instance, if it can sort, the SPI implementation should report the results as sorted, but unfiltered.
If necessary the VCR may sort/filter the query results in memory to ensure the query results are sorted/filtered as the client requests.
Use the following objects for sorting and filtering:
QueryCriteria
– Some methods pass a QueryCriteria
object as a means for a caller to request sorting and filtering of results. These methods include SortCriteria
and FilterCriteria
objects. Currently, only a single SortCriteria
or FilterCriteria
can be specified; multi-criteria sorting or filtering is not supported.SortCriteria
– Provides the ability for ascending or descending sorting on a specific property (criteria). A sortResults
flag indicates whether the results are sorted or unsorted. SortCriteria
also includes a property
that is one of the JavaBean properties on the data object.FilterCriteria
– Provides the ability to filter on a specific property (criteria). A filterResults
flag indicates whether the results are filtered or unfiltered. FilterCriteria
includes a property
that is one of the JavaBean properties on the data object, and a filterMethod
that indicates the filter operation, such as begins with, contains, equals, greater than, unfiltered, and so on.QueryResult
– Represents a set of results returned by the SPI implementation. QueryResult
also includes a QueryCriteria
object that describes how the results are sorted and filtered (or neither), plus an ordered list of the data objects.
![]() ![]() ![]() |